From 930f2fcc6fe0c3f5f97182916f2256d131b4566a Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Thu, 28 Jan 2021 11:31:50 +0800 Subject: [PATCH 1/3] Support vendoring go.opentelemetry.io/collector Add a script to vendor go.opentelemetry.io/collector, along with mixin sources in "internal/.otel_collector_mixin", into "internal/otel_collector". The script will update go.mod to use this as a local replacement. Run this as part of the "go-generate" make target. We also add a requirement on go-licence-detector 0.5.0, which added support for local module replacements. --- .ci/docker/Makefile | 1 + .ci/docker/golang-mage/Dockerfile | 2 + .gitignore | 1 + Makefile | 4 +- go.mod | 1 + .../receiver/otlpreceiver/mixin.go | 67 +++++++++++++++++++ .../service/defaultcomponents/defaults.go | 26 +++++++ main.go | 1 + script/vendor_otel.sh | 18 +++++ tests/Dockerfile | 1 + tools.go | 1 + 11 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 internal/.otel_collector_mixin/receiver/otlpreceiver/mixin.go create mode 100644 internal/.otel_collector_mixin/service/defaultcomponents/defaults.go create mode 100644 script/vendor_otel.sh diff --git a/.ci/docker/Makefile b/.ci/docker/Makefile index d9ed9f05bed..35c4071eeed 100644 --- a/.ci/docker/Makefile +++ b/.ci/docker/Makefile @@ -45,6 +45,7 @@ test-golang-mage: prepare-test ## Run the tests for the specific app cp -f ../../go.* golang-mage mkdir -p golang-mage/approvaltest && cp -f ../../approvaltest/go.* golang-mage/approvaltest mkdir -p golang-mage/systemtest && cp -f ../../systemtest/go.* golang-mage/systemtest + mkdir -p golang-mage/internal/otel_collector && cp -f ../../internal/otel_collector/go.* golang-mage/internal/otel_collector @DOCKERFILE=golang-mage bats-core/bin/bats --tap tests | tee target/results.tap @$(MAKE) -s convert-tests-results diff --git a/.ci/docker/golang-mage/Dockerfile b/.ci/docker/golang-mage/Dockerfile index 55b09fd5722..74bf0cd0a69 100644 --- a/.ci/docker/golang-mage/Dockerfile +++ b/.ci/docker/golang-mage/Dockerfile @@ -7,10 +7,12 @@ WORKDIR $TOOLS COPY go.mod go.sum ./ COPY approvaltest/go.mod approvaltest/go.sum ./approvaltest/ COPY systemtest/go.mod systemtest/go.sum ./systemtest/ +COPY internal/otel_collector/go.mod internal/otel_collector/go.sum ./internal/otel_collector/ RUN go mod download RUN cd approvaltest && go mod download RUN cd systemtest && go mod download +RUN cd internal/otel_collector && go mod download RUN apt-get update -y -qq \ && apt-get install -y -qq python3 python3-pip python3-venv \ diff --git a/.gitignore b/.gitignore index 13d56edf4cf..6bc8b9e9380 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.ci/docker/golang-mage/go.* /.ci/docker/golang-mage/approvaltest/ /.ci/docker/golang-mage/systemtest/ +/.ci/docker/golang-mage/internal/otel_collector/ **/*.idea /build /data diff --git a/Makefile b/Makefile index 587600cab25..4de38b9f8a7 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,7 @@ NOTICE.txt: $(PYTHON) go.mod .PHONY: add-headers add-headers: $(GOLICENSER) ifndef CHECK_HEADERS_DISABLED - @$(GOLICENSER) -exclude x-pack + @$(GOLICENSER) -exclude x-pack -exclude internal/otel_collector @$(GOLICENSER) -license Elastic x-pack endif @@ -222,7 +222,7 @@ check-changelogs: $(PYTHON) .PHONY: check-headers check-headers: $(GOLICENSER) ifndef CHECK_HEADERS_DISABLED - @$(GOLICENSER) -d -exclude build -exclude x-pack + @$(GOLICENSER) -d -exclude build -exclude x-pack -exclude internal/otel_collector @$(GOLICENSER) -d -exclude build -license Elastic x-pack endif diff --git a/go.mod b/go.mod index e14ab251a1a..34212d0cd9e 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( go.elastic.co/apm/module/apmgrpc v1.7.0 go.elastic.co/apm/module/apmhttp v1.7.2 go.elastic.co/fastjson v1.1.0 + go.elastic.co/go-licence-detector v0.5.0 go.opentelemetry.io/collector v0.17.0 go.uber.org/atomic v1.7.0 go.uber.org/multierr v1.6.0 // indirect diff --git a/internal/.otel_collector_mixin/receiver/otlpreceiver/mixin.go b/internal/.otel_collector_mixin/receiver/otlpreceiver/mixin.go new file mode 100644 index 00000000000..06be24b7e24 --- /dev/null +++ b/internal/.otel_collector_mixin/receiver/otlpreceiver/mixin.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "context" + + gatewayruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + collectorlog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + collectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + "go.opentelemetry.io/collector/receiver/otlpreceiver/logs" + "go.opentelemetry.io/collector/receiver/otlpreceiver/metrics" + "go.opentelemetry.io/collector/receiver/otlpreceiver/trace" +) + +// RegisterTraceReceiver registers the trace receiver with a gRPC server and/or grpc-gateway mux, if non-nil. +func RegisterTraceReceiver(ctx context.Context, receiver *trace.Receiver, serverGRPC *grpc.Server, gatewayMux *gatewayruntime.ServeMux) error { + if serverGRPC != nil { + collectortrace.RegisterTraceServiceServer(serverGRPC, receiver) + } + if gatewayMux != nil { + err := collectortrace.RegisterTraceServiceHandlerServer(ctx, gatewayMux, receiver) + if err != nil { + return err + } + // Also register an alias handler. This fixes bug https://github.com/open-telemetry/opentelemetry-collector/issues/1968 + return collectortrace.RegisterTraceServiceHandlerServerAlias(ctx, gatewayMux, receiver) + } + return nil +} + +// RegisterMetricsReceiver registers the metrics receiver with a gRPC server and/or grpc-gateway mux, if non-nil. +func RegisterMetricsReceiver(ctx context.Context, receiver *metrics.Receiver, serverGRPC *grpc.Server, gatewayMux *gatewayruntime.ServeMux) error { + if serverGRPC != nil { + collectormetrics.RegisterMetricsServiceServer(serverGRPC, receiver) + } + if gatewayMux != nil { + return collectormetrics.RegisterMetricsServiceHandlerServer(ctx, gatewayMux, receiver) + } + return nil +} + +// RegisterLogsReceiver registers the logs receiver with a gRPC server and/or grpc-gateway mux, if non-nil. +func RegisterLogsReceiver(ctx context.Context, receiver *logs.Receiver, serverGRPC *grpc.Server, gatewayMux *gatewayruntime.ServeMux) error { + if serverGRPC != nil { + collectorlog.RegisterLogsServiceServer(serverGRPC, receiver) + } + if gatewayMux != nil { + return collectorlog.RegisterLogsServiceHandlerServer(ctx, gatewayMux, receiver) + } + return nil +} diff --git a/internal/.otel_collector_mixin/service/defaultcomponents/defaults.go b/internal/.otel_collector_mixin/service/defaultcomponents/defaults.go new file mode 100644 index 00000000000..23dbabfbd49 --- /dev/null +++ b/internal/.otel_collector_mixin/service/defaultcomponents/defaults.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package defaultcomponents composes the default set of components used by the otel service +package defaultcomponents + +import ( + "go.opentelemetry.io/collector/component" +) + +// Components returns the default set of components used by the +// OpenTelemetry collector. +func Components() (component.Factories, error) { + return component.Factories{}, nil +} diff --git a/main.go b/main.go index 9b21eba3b5d..e81afade953 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ package main //go:generate go run model/modeldecoder/generator/cmd/main.go +//go:generate bash script/vendor_otel.sh import ( "os" diff --git a/script/vendor_otel.sh b/script/vendor_otel.sh new file mode 100644 index 00000000000..1c39c86f8b0 --- /dev/null +++ b/script/vendor_otel.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -xe + +go mod edit -dropreplace go.opentelemetry.io/collector +go mod download go.opentelemetry.io/collector + +REPO_ROOT=$(go list -m -f {{.Dir}} github.com/elastic/apm-server) +MIXIN_DIR=$REPO_ROOT/internal/.otel_collector_mixin +TARGET_DIR=$REPO_ROOT/internal/otel_collector +MODULE_DIR=$(go list -m -f {{.Dir}} go.opentelemetry.io/collector) + +rm -fr $TARGET_DIR +mkdir $TARGET_DIR +rsync -cr --no-perms --no-group --chmod=ugo=rwX --delete $MODULE_DIR/* $TARGET_DIR +rsync -cr --no-perms --no-group --chmod=ugo=rwX $MIXIN_DIR/* $TARGET_DIR + +go mod edit -replace go.opentelemetry.io/collector=./internal/otel_collector diff --git a/tests/Dockerfile b/tests/Dockerfile index 20698ec57ed..4cd96dc63d0 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -27,6 +27,7 @@ WORKDIR $HOME COPY go.mod go.sum ./ COPY approvaltest/go.mod approvaltest/go.sum ./approvaltest/ COPY systemtest/go.mod systemtest/go.sum ./systemtest/ +COPY internal/otel_collector/go.mod internal/otel_collector/go.sum ./internal/otel_collector/ RUN go mod download RUN cd approvaltest && go mod download RUN cd systemtest && go mod download diff --git a/tools.go b/tools.go index 64bcf2f3c55..fc786c281c6 100644 --- a/tools.go +++ b/tools.go @@ -29,4 +29,5 @@ import ( _ "github.com/t-yuki/gocover-cobertura" _ "github.com/elastic/go-licenser" + _ "go.elastic.co/go-licence-detector" ) From dcda5c95957bee1ddf3a3ccc885c6d885d91d945 Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Thu, 28 Jan 2021 11:58:09 +0800 Subject: [PATCH 2/3] Vendor go.opentelemetry.io/collector --- NOTICE.txt | 2 +- go.mod | 2 + go.sum | 2 + internal/otel_collector/CHANGELOG.md | 653 ++ internal/otel_collector/CONTRIBUTING.md | 289 + internal/otel_collector/LICENSE | 202 + internal/otel_collector/Makefile | 309 + internal/otel_collector/Makefile.Common | 94 + internal/otel_collector/README.md | 90 + internal/otel_collector/client/client.go | 74 + internal/otel_collector/client/client_test.go | 60 + .../otel_collector/cmd/otelcol/Dockerfile | 16 + .../otel_collector/cmd/otelcol/config.yaml | 55 + internal/otel_collector/cmd/otelcol/main.go | 59 + .../otel_collector/cmd/otelcol/main_others.go | 23 + .../cmd/otelcol/main_windows.go | 47 + .../cmd/pdatagen/internal/base_fields.go | 343 + .../cmd/pdatagen/internal/base_slices.go | 609 ++ .../cmd/pdatagen/internal/base_structs.go | 173 + .../cmd/pdatagen/internal/common_structs.go | 124 + .../cmd/pdatagen/internal/files.go | 106 + .../cmd/pdatagen/internal/log_structs.go | 138 + .../cmd/pdatagen/internal/metrics_structs.go | 493 ++ .../cmd/pdatagen/internal/resource_structs.go | 45 + .../cmd/pdatagen/internal/trace_structs.go | 259 + internal/otel_collector/cmd/pdatagen/main.go | 42 + .../otel_collector/component/component.go | 135 + .../component/component_test.go | 15 + .../component/componenterror/errors.go | 63 + .../component/componenterror/errors_test.go | 70 + .../component/componenthelper/component.go | 64 + .../componenthelper/component_test.go | 69 + .../componenttest/application_start_info.go | 29 + .../component/componenttest/doc.go | 17 + .../component/componenttest/docs.go | 102 + .../component/componenttest/docs_test.go | 177 + .../componenttest/error_waiting_host.go | 70 + .../componenttest/error_waiting_host_test.go | 50 + .../componenttest/example_factories.go | 508 ++ .../componenttest/example_factories_test.go | 61 + .../component/componenttest/nop_host.go | 52 + .../component/componenttest/nop_host_test.go | 36 + .../componenttest/testdata/invalid_go.txt | 6 + .../componenttest/testdata/valid_go.txt | 9 + internal/otel_collector/component/exporter.go | 99 + .../otel_collector/component/exporter_test.go | 90 + .../otel_collector/component/extension.go | 74 + .../otel_collector/component/factories.go | 93 + .../otel_collector/component/processor.go | 116 + .../component/processor_test.go | 91 + internal/otel_collector/component/receiver.go | 99 + .../otel_collector/component/receiver_test.go | 91 + internal/otel_collector/config/config.go | 780 ++ internal/otel_collector/config/config_test.go | 797 +++ .../config/configauth/README.md | 17 + .../config/configauth/authenticator.go | 94 + .../config/configauth/authenticator_test.go | 195 + .../config/configauth/configauth.go | 74 + .../config/configauth/configauth_test.go | 74 + .../config/configauth/context.go | 37 + .../config/configauth/context_test.go | 94 + .../config/configauth/oidc_authenticator.go | 245 + .../configauth/oidc_authenticator_test.go | 548 ++ .../config/configauth/oidc_server_test.go | 136 + .../config/configcheck/configcheck.go | 193 + .../config/configcheck/configcheck_test.go | 203 + .../config/configerror/configerror.go | 23 + .../config/configgrpc/README.md | 62 + .../config/configgrpc/bearer_token.go | 46 + .../config/configgrpc/bearer_token_test.go | 41 + .../config/configgrpc/configgrpc.go | 313 + .../config/configgrpc/configgrpc_test.go | 523 ++ .../otel_collector/config/configgrpc/gzip.go | 20 + .../config/configgrpc/testdata/ca.crt | 20 + .../config/configgrpc/testdata/client.crt | 21 + .../config/configgrpc/testdata/client.key | 27 + .../config/configgrpc/testdata/server.crt | 21 + .../config/configgrpc/testdata/server.key | 27 + .../config/confighttp/README.md | 56 + .../config/confighttp/confighttp.go | 168 + .../config/confighttp/confighttp_test.go | 427 ++ .../config/confighttp/testdata/ca.crt | 20 + .../config/confighttp/testdata/client.crt | 21 + .../config/confighttp/testdata/client.key | 27 + .../config/confighttp/testdata/server.crt | 21 + .../config/confighttp/testdata/server.key | 27 + .../config/configmodels/configmodels.go | 223 + .../otel_collector/config/confignet/README.md | 18 + .../config/confignet/confignet.go | 59 + .../config/confignet/confignet_test.go | 90 + .../config/configtelemetry/configtelemetry.go | 127 + .../configtelemetry/configtelemetry_test.go | 167 + .../config/configtest/configtest.go | 51 + .../config/configtest/configtest_test.go | 127 + .../config/configtest/testdata/config.yaml | 28 + .../otel_collector/config/configtls/README.md | 111 + .../config/configtls/configtls.go | 151 + .../config/configtls/configtls_test.go | 181 + .../config/configtls/testdata/test-cert.pem | 25 + .../config/configtls/testdata/test-key.pem | 28 + .../config/configtls/testdata/testCA-bad.txt | 3 + .../config/configtls/testdata/testCA.pem | 13 + .../config/testdata/duplicate-exporter.yaml | 13 + .../config/testdata/duplicate-extension.yaml | 3 + .../config/testdata/duplicate-pipeline.yaml | 16 + .../config/testdata/duplicate-processor.yaml | 13 + .../config/testdata/duplicate-receiver.yaml | 13 + .../config/testdata/empty-config.yaml | 0 .../testdata/invalid-exporter-section.yaml | 20 + .../testdata/invalid-exporter-sub-config.yaml | 13 + .../testdata/invalid-extension-name.yaml | 15 + .../testdata/invalid-extension-section.yaml | 21 + .../invalid-extension-sub-config.yaml | 16 + .../testdata/invalid-pipeline-section.yaml | 20 + .../testdata/invalid-pipeline-sub-config.yaml | 9 + .../invalid-pipeline-type-and-name.yaml | 13 + .../testdata/invalid-pipeline-type.yaml | 13 + .../testdata/invalid-processor-section.yaml | 21 + .../invalid-processor-sub-config.yaml | 13 + .../testdata/invalid-receiver-name.yaml | 12 + .../testdata/invalid-receiver-reference.yaml | 10 + .../testdata/invalid-receiver-section.yaml | 21 + .../testdata/invalid-receiver-sub-config.yaml | 13 + .../testdata/invalid-sequence-value.yaml | 14 + .../invalid-service-extensions-value.yaml | 18 + .../testdata/invalid-service-section.yaml | 21 + .../testdata/invalid-top-level-section.yaml | 21 + ...etric-pipeline-cannot-have-processors.yaml | 12 + .../config/testdata/missing-all-sections.yaml | 5 + .../missing-exporter-name-after-slash.yaml | 10 + .../config/testdata/missing-exporters.yaml | 9 + .../testdata/missing-extension-type.yaml | 2 + .../config/testdata/missing-pipelines.yaml | 8 + .../testdata/missing-processor-type.yaml | 10 + .../config/testdata/missing-processors.yaml | 9 + .../testdata/missing-receiver-type.yaml | 10 + .../config/testdata/missing-receivers.yaml | 8 + .../config/testdata/multiproto-config.yaml | 26 + .../pipeline-exporter-not-exists.yaml | 11 + .../testdata/pipeline-must-have-exporter.yaml | 11 + .../pipeline-must-have-exporter2.yaml | 11 + .../testdata/pipeline-must-have-receiver.yaml | 11 + .../pipeline-must-have-receiver2.yaml | 11 + .../pipeline-processor-not-exists.yaml | 15 + .../testdata/simple-config-with-all-env.yaml | 51 + .../simple-config-with-escaped-env.yaml | 62 + .../testdata/simple-config-with-no-env.yaml | 51 + .../simple-config-with-partial-env.yaml | 51 + .../testdata/unknown-exporter-type.yaml | 12 + .../testdata/unknown-extension-type.yaml | 2 + .../testdata/unknown-processor-type.yaml | 12 + .../testdata/unknown-receiver-type.yaml | 15 + .../config/testdata/valid-config.yaml | 29 + internal/otel_collector/consumer/consumer.go | 42 + .../consumer/consumerdata/consumerdata.go | 40 + .../consumer/consumererror/partialerror.go | 68 + .../consumererror/partialerror_test.go | 48 + .../consumererror/partialscrapeerror.go | 43 + .../consumererror/partialscrapeerror_test.go | 40 + .../consumer/consumererror/permanenterror.go | 45 + .../consumererror/permanenterror_test.go | 35 + .../consumer/consumertest/nop.go | 55 + .../consumer/consumertest/nop_test.go | 42 + .../consumer/consumertest/sink.go | 183 + .../consumer/consumertest/sink_test.go | 99 + .../otel_collector/consumer/pdata/common.go | 790 ++ .../consumer/pdata/common_test.go | 1131 +++ internal/otel_collector/consumer/pdata/doc.go | 32 + .../consumer/pdata/generated_common.go | 187 + .../consumer/pdata/generated_common_test.go | 185 + .../consumer/pdata/generated_log.go | 612 ++ .../consumer/pdata/generated_log_test.go | 569 ++ .../consumer/pdata/generated_metrics.go | 2491 +++++++ .../consumer/pdata/generated_metrics_test.go | 2220 ++++++ .../consumer/pdata/generated_resource.go | 56 + .../consumer/pdata/generated_resource_test.go | 48 + .../consumer/pdata/generated_trace.go | 1141 +++ .../consumer/pdata/generated_trace_test.go | 1019 +++ internal/otel_collector/consumer/pdata/log.go | 142 + .../otel_collector/consumer/pdata/log_test.go | 134 + .../otel_collector/consumer/pdata/metric.go | 314 + .../consumer/pdata/metric_test.go | 988 +++ .../otel_collector/consumer/pdata/spanid.go | 45 + .../consumer/pdata/timestamp.go | 52 + .../consumer/pdata/timestamp_test.go | 42 + .../otel_collector/consumer/pdata/trace.go | 188 + .../consumer/pdata/trace_test.go | 248 + .../otel_collector/consumer/pdata/traceid.go | 45 + .../otel_collector/consumer/simple/metrics.go | 360 + .../consumer/simple/metrics_test.go | 516 ++ internal/otel_collector/docs/design.md | 232 + .../docs/images/design-exporters.png | Bin 0 -> 16807 bytes .../docs/images/design-pipelines.png | Bin 0 -> 19661 bytes .../docs/images/design-processors.png | Bin 0 -> 15971 bytes .../docs/images/design-receivers.png | Bin 0 -> 17407 bytes .../docs/images/design-service-lifecycle.png | Bin 0 -> 21842 bytes ...pentelemetry-service-deployment-models.png | Bin 0 -> 78430 bytes .../docs/images/zpages-example.png | Bin 0 -> 373980 bytes .../otel_collector/docs/metric-metadata.md | 19 + .../otel_collector/docs/metric-metadata.yaml | 33 + .../docs/migrating-from-opencensus.md | 47 + internal/otel_collector/docs/monitoring.md | 69 + internal/otel_collector/docs/observability.md | 91 + internal/otel_collector/docs/performance.md | 72 + internal/otel_collector/docs/release.md | 39 + internal/otel_collector/docs/roadmap.md | 27 + .../otel_collector/docs/service-extensions.md | 146 + .../otel_collector/docs/troubleshooting.md | 260 + internal/otel_collector/docs/vision.md | 25 + internal/otel_collector/examples/README.md | 5 + internal/otel_collector/examples/demo/.env | 2 + .../otel_collector/examples/demo/README.md | 49 + .../examples/demo/docker-compose.yaml | 83 + .../examples/demo/otel-agent-config.yaml | 38 + .../examples/demo/otel-collector-config.yaml | 47 + .../examples/demo/prometheus.yaml | 6 + .../examples/k8s/otel-config.yaml | 245 + .../examples/local/otel-config.yaml | 55 + internal/otel_collector/exporter/README.md | 109 + internal/otel_collector/exporter/doc.go | 22 + .../exporter/exporterhelper/README.md | 28 + .../exporter/exporterhelper/common.go | 223 + .../exporter/exporterhelper/common_test.go | 65 + .../exporter/exporterhelper/constants.go | 32 + .../exporter/exporterhelper/factory.go | 153 + .../exporter/exporterhelper/factory_test.go | 112 + .../exporter/exporterhelper/logshelper.go | 114 + .../exporterhelper/logshelper_test.go | 232 + .../exporter/exporterhelper/metricshelper.go | 126 + .../exporterhelper/metricshelper_test.go | 253 + .../exporter/exporterhelper/queued_retry.go | 316 + .../exporterhelper/queued_retry_test.go | 462 ++ .../exporterhelper/resource_to_label.go | 116 + .../exporterhelper/resource_to_label_test.go | 75 + .../exporter/exporterhelper/tracehelper.go | 121 + .../exporterhelper/tracehelper_test.go | 252 + .../exporter/fileexporter/README.md | 26 + .../exporter/fileexporter/config.go | 27 + .../exporter/fileexporter/config_test.go | 52 + .../exporter/fileexporter/factory.go | 100 + .../exporter/fileexporter/factory_test.go | 64 + .../exporter/fileexporter/file_exporter.go | 84 + .../fileexporter/file_exporter_test.go | 217 + .../fileexporter/testdata/config.yaml | 26 + .../exporter/jaegerexporter/README.md | 49 + .../exporter/jaegerexporter/config.go | 31 + .../exporter/jaegerexporter/config_test.go | 81 + .../exporter/jaegerexporter/doc.go | 17 + .../exporter/jaegerexporter/exporter.go | 102 + .../exporter/jaegerexporter/exporter_test.go | 278 + .../exporter/jaegerexporter/factory.go | 77 + .../exporter/jaegerexporter/factory_test.go | 66 + .../exporter/jaegerexporter/testdata/ca.crt | 20 + .../jaegerexporter/testdata/client.crt | 21 + .../jaegerexporter/testdata/client.key | 27 + .../jaegerexporter/testdata/config.yaml | 28 + .../jaegerexporter/testdata/server.crt | 21 + .../jaegerexporter/testdata/server.key | 27 + .../jaegerexporter/testdata/test_cert.pem | 29 + .../exporter/kafkaexporter/README.md | 69 + .../exporter/kafkaexporter/authentication.go | 95 + .../kafkaexporter/authentication_test.go | 93 + .../exporter/kafkaexporter/config.go | 71 + .../exporter/kafkaexporter/config_test.go | 78 + .../exporter/kafkaexporter/factory.go | 144 + .../exporter/kafkaexporter/factory_test.go | 115 + .../kafkaexporter/jaeger_marshaller.go | 98 + .../kafkaexporter/jaeger_marshaller_test.go | 95 + .../exporter/kafkaexporter/kafka_exporter.go | 154 + .../kafkaexporter/kafka_exporter_test.go | 218 + .../exporter/kafkaexporter/marshaller.go | 62 + .../exporter/kafkaexporter/marshaller_test.go | 54 + .../exporter/kafkaexporter/otlp_marshaller.go | 60 + .../kafkaexporter/otlp_marshaller_test.go | 59 + .../kafkaexporter/testdata/config.yaml | 37 + .../exporter/loggingexporter/README.md | 29 + .../exporter/loggingexporter/config.go | 33 + .../exporter/loggingexporter/config_test.go | 54 + .../exporter/loggingexporter/factory.go | 111 + .../exporter/loggingexporter/factory_test.go | 60 + .../loggingexporter/logging_exporter.go | 508 ++ .../loggingexporter/logging_exporter_test.go | 80 + .../loggingexporter/testdata/config.yaml | 22 + .../exporter/opencensusexporter/README.md | 47 + .../exporter/opencensusexporter/config.go | 30 + .../opencensusexporter/config_test.go | 76 + .../exporter/opencensusexporter/factory.go | 63 + .../opencensusexporter/factory_test.go | 180 + .../exporter/opencensusexporter/opencensus.go | 285 + .../opencensusexporter/opencensus_test.go | 227 + .../opencensusexporter/testdata/config.yaml | 29 + .../opencensusexporter/testdata/test_cert.pem | 29 + .../exporter/otlpexporter/README.md | 51 + .../exporter/otlpexporter/config.go | 31 + .../exporter/otlpexporter/config_test.go | 95 + .../exporter/otlpexporter/factory.go | 133 + .../exporter/otlpexporter/factory_test.go | 198 + .../exporter/otlpexporter/otlp.go | 248 + .../exporter/otlpexporter/otlp_test.go | 535 ++ .../otlpexporter/testdata/config.yaml | 41 + .../otlpexporter/testdata/test_cert.pem | 29 + .../exporter/otlphttpexporter/README.md | 50 + .../exporter/otlphttpexporter/config.go | 38 + .../exporter/otlphttpexporter/config_test.go | 85 + .../exporter/otlphttpexporter/factory.go | 153 + .../exporter/otlphttpexporter/factory_test.go | 161 + .../exporter/otlphttpexporter/otlp.go | 206 + .../exporter/otlphttpexporter/otlp_test.go | 416 ++ .../otlphttpexporter/testdata/config.yaml | 37 + .../otlphttpexporter/testdata/test_cert.pem | 29 + .../exporter/prometheusexporter/README.md | 31 + .../exporter/prometheusexporter/config.go | 38 + .../prometheusexporter/config_test.go | 58 + .../exporter/prometheusexporter/factory.go | 98 + .../prometheusexporter/factory_test.go | 45 + .../exporter/prometheusexporter/prometheus.go | 85 + .../prometheusexporter/prometheus_test.go | 248 + .../prometheusexporter/testdata/config.yaml | 22 + .../prometheusremotewriteexporter/DESIGN.md | 288 + .../prometheusremotewriteexporter/README.md | 54 + .../prometheusremotewriteexporter/config.go | 39 + .../config_test.go | 87 + .../prometheusremotewriteexporter/exporter.go | 345 + .../exporter_test.go | 751 ++ .../prometheusremotewriteexporter/factory.go | 90 + .../factory_test.go | 91 + .../prometheusremotewriteexporter/helper.go | 489 ++ .../helper_test.go | 363 + .../img/cortex.png | Bin 0 -> 73392 bytes .../img/timeseries.png | Bin 0 -> 9001 bytes .../testdata/config.yaml | 37 + .../testutil_test.go | 578 ++ .../exporter/zipkinexporter/README.md | 51 + .../exporter/zipkinexporter/config.go | 36 + .../exporter/zipkinexporter/config_test.go | 82 + .../exporter/zipkinexporter/factory.go | 89 + .../exporter/zipkinexporter/factory_test.go | 49 + .../zipkinexporter/testdata/config.yaml | 29 + .../exporter/zipkinexporter/testutils_test.go | 53 + .../exporter/zipkinexporter/zipkin.go | 94 + .../exporter/zipkinexporter/zipkin_test.go | 359 + internal/otel_collector/extension/README.md | 107 + .../extension/extensionhelper/factory.go | 99 + .../extension/extensionhelper/factory_test.go | 91 + .../extension/fluentbitextension/README.md | 65 + .../extension/fluentbitextension/config.go | 50 + .../fluentbitextension/config_test.go | 56 + .../extension/fluentbitextension/factory.go | 50 + .../fluentbitextension/factory_test.go | 51 + .../extension/fluentbitextension/process.go | 224 + .../fluentbitextension/process_linux.go | 30 + .../fluentbitextension/process_linux_test.go | 177 + .../fluentbitextension/process_others.go | 23 + .../fluentbitextension/testdata/config.yaml | 20 + .../extension/healthcheckextension/README.md | 19 + .../extension/healthcheckextension/config.go | 29 + .../healthcheckextension/config_test.go | 56 + .../extension/healthcheckextension/doc.go | 18 + .../extension/healthcheckextension/factory.go | 73 + .../healthcheckextension/factory_test.go | 78 + .../healthcheckextension.go | 88 + .../healthcheckextension_test.go | 142 + .../healthcheckextension/testdata/config.yaml | 20 + .../extension/pprofextension/README.md | 32 + .../extension/pprofextension/config.go | 43 + .../extension/pprofextension/config_test.go | 58 + .../extension/pprofextension/doc.go | 17 + .../extension/pprofextension/factory.go | 77 + .../extension/pprofextension/factory_test.go | 78 + .../pprofextension/pprofextension.go | 79 + .../pprofextension/pprofextension_test.go | 110 + .../pprofextension/testdata/config.yaml | 22 + .../extension/zpagesextension/README.md | 20 + .../extension/zpagesextension/config.go | 29 + .../extension/zpagesextension/config_test.go | 56 + .../extension/zpagesextension/doc.go | 17 + .../extension/zpagesextension/factory.go | 77 + .../extension/zpagesextension/factory_test.go | 78 + .../zpagesextension/testdata/config.yaml | 20 + .../zpagesextension/zpagesextension.go | 75 + .../zpagesextension/zpagesextension_test.go | 108 + internal/otel_collector/go.mod | 64 + internal/otel_collector/go.sum | 1480 ++++ .../internal/buildscripts/gen-certs.sh | 125 + .../buildscripts/packaging/fpm/Dockerfile | 16 + .../buildscripts/packaging/fpm/common.sh | 59 + .../buildscripts/packaging/fpm/deb/README.md | 13 + .../buildscripts/packaging/fpm/deb/build.sh | 52 + .../packaging/fpm/otel-collector.service | 14 + .../buildscripts/packaging/fpm/postinstall.sh | 22 + .../buildscripts/packaging/fpm/preinstall.sh | 17 + .../packaging/fpm/preuninstall.sh | 20 + .../buildscripts/packaging/fpm/rpm/README.md | 13 + .../buildscripts/packaging/fpm/rpm/build.sh | 52 + .../buildscripts/packaging/fpm/test.sh | 87 + .../buildscripts/packaging/msi/make.ps1 | 75 + .../packaging/msi/opentelemetry-collector.wxs | 59 + .../packaging/msi/opentelemetry.ico | Bin 0 -> 275598 bytes .../internal/collector/telemetry/telemetry.go | 76 + .../otel_collector/internal/data/.gitignore | 1 + .../otel_collector/internal/data/bytesid.go | 67 + .../collector/logs/v1/logs_service.pb.go | 558 ++ .../collector/logs/v1/logs_service.pb.gw.go | 163 + .../metrics/v1/metrics_service.pb.go | 558 ++ .../metrics/v1/metrics_service.pb.gw.go | 163 + .../collector/trace/v1/trace_config.pb.go | 1262 ++++ .../collector/trace/v1/trace_service.pb.go | 559 ++ .../collector/trace/v1/trace_service.pb.gw.go | 163 + .../trace/v1/trace_service_gateway_aliases.go | 80 + .../common/v1/common.pb.go | 1781 +++++ .../logs/v1/logs.pb.go | 1387 ++++ .../metrics/v1/metrics.pb.go | 6374 +++++++++++++++++ .../resource/v1/resource.pb.go | 381 + .../trace/v1/trace.pb.go | 2667 +++++++ .../otel_collector/internal/data/spanid.go | 104 + .../internal/data/spanid_test.go | 129 + .../otel_collector/internal/data/traceid.go | 106 + .../internal/data/traceid_test.go | 130 + .../goldendataset/generator_commons.go | 113 + .../internal/goldendataset/metric_gen.go | 269 + .../internal/goldendataset/metric_gen_test.go | 130 + .../internal/goldendataset/pict_metric_gen.go | 100 + .../goldendataset/pict_metric_gen_test.go | 93 + .../goldendataset/pict_metrics_input_defs.go | 69 + .../goldendataset/pict_tracing_input_defs.go | 170 + .../goldendataset/resource_generator.go | 170 + .../goldendataset/resource_generator_test.go | 49 + .../internal/goldendataset/span_generator.go | 529 ++ .../goldendataset/span_generator_test.go | 80 + .../testdata/generated_pict_pairs_metrics.txt | 26 + .../testdata/generated_pict_pairs_spans.txt | 307 + .../testdata/generated_pict_pairs_traces.txt | 33 + .../testdata/pict_input_metrics.txt | 4 + .../testdata/pict_input_spans.txt | 14 + .../testdata/pict_input_traces.txt | 3 + .../goldendataset/traces_generator.go | 138 + .../goldendataset/traces_generator_test.go | 31 + .../internal/middleware/compression.go | 95 + .../internal/middleware/compression_test.go | 157 + .../otel_collector/internal/otlp_wrapper.go | 32 + .../internal/processor/filterconfig/config.go | 161 + .../processor/filterconfig/config_test.go | 15 + .../internal/processor/filterexpr/matcher.go | 172 + .../processor/filterexpr/matcher_test.go | 345 + .../processor/filterhelper/filterhelper.go | 40 + .../filterhelper/filterhelper_test.go | 63 + .../internal/processor/filterlog/filterlog.go | 84 + .../processor/filterlog/filterlog_test.go | 183 + .../filtermatcher/attributematcher.go | 129 + .../processor/filtermatcher/filtermatcher.go | 103 + .../filtermatcher/filtermatcher_test.go | 394 + .../internal/processor/filtermetric/config.go | 52 + .../processor/filtermetric/config_test.go | 91 + .../internal/processor/filtermetric/doc.go | 16 + .../processor/filtermetric/expr_matcher.go | 49 + .../processor/filtermetric/filtermetric.go | 32 + .../filtermetric/filtermetric_test.go | 97 + .../processor/filtermetric/helpers_test.go | 26 + .../processor/filtermetric/name_matcher.go | 47 + .../filtermetric/testdata/config.yaml | 24 + .../internal/processor/filterset/config.go | 57 + .../processor/filterset/config_test.go | 90 + .../internal/processor/filterset/doc.go | 16 + .../internal/processor/filterset/filterset.go | 22 + .../processor/filterset/regexp/config.go | 25 + .../processor/filterset/regexp/config_test.go | 56 + .../processor/filterset/regexp/doc.go | 16 + .../filterset/regexp/regexpfilterset.go | 96 + .../filterset/regexp/regexpfilterset_test.go | 208 + .../filterset/regexp/testdata/config.yaml | 10 + .../processor/filterset/strict/doc.go | 16 + .../filterset/strict/strictfilterset.go | 47 + .../filterset/strict/strictfilterset_test.go | 83 + .../processor/filterset/testdata/config.yaml | 16 + .../filterset/testdata/config_invalid.yaml | 6 + .../processor/filterspan/filterspan.go | 134 + .../processor/filterspan/filterspan_test.go | 261 + .../internal/testdata/common.go | 179 + .../otel_collector/internal/testdata/log.go | 318 + .../internal/testdata/log_test.go | 90 + .../internal/testdata/metric.go | 635 ++ .../internal/testdata/metric_test.go | 105 + .../internal/testdata/resource.go | 41 + .../otel_collector/internal/testdata/trace.go | 313 + .../internal/testdata/trace_test.go | 90 + .../internal/version/version.go | 77 + .../internal/version/version_test.go | 30 + internal/otel_collector/obsreport/doc.go | 112 + .../otel_collector/obsreport/observability.go | 32 + .../otel_collector/obsreport/obsreport.go | 163 + .../obsreport/obsreport_exporter.go | 190 + .../obsreport/obsreport_processor.go | 246 + .../obsreport/obsreport_receiver.go | 364 + .../obsreport/obsreport_scraper.go | 132 + .../obsreport/obsreport_test.go | 667 ++ .../obsreport/obsreporttest/obsreporttest.go | 205 + .../obsreporttest/obsreporttest_test.go | 121 + internal/otel_collector/processor/README.md | 234 + .../processor/attributesprocessor/README.md | 106 + .../attributesprocessor/attributes_log.go | 88 + .../attributes_log_test.go | 423 ++ .../attributesprocessor/attributes_trace.go | 64 + .../attributes_trace_test.go | 452 ++ .../processor/attributesprocessor/config.go | 39 + .../attributesprocessor/config_test.go | 237 + .../processor/attributesprocessor/doc.go | 17 + .../processor/attributesprocessor/factory.go | 113 + .../attributesprocessor/factory_test.go | 149 + .../attributesprocessor/testdata/config.yaml | 316 + .../processor/batchprocessor/README.md | 38 + .../batchprocessor/batch_processor.go | 348 + .../batchprocessor/batch_processor_test.go | 691 ++ .../processor/batchprocessor/config.go | 36 + .../processor/batchprocessor/config_test.go | 60 + .../processor/batchprocessor/factory.go | 88 + .../processor/batchprocessor/factory_test.go | 52 + .../processor/batchprocessor/metrics.go | 79 + .../processor/batchprocessor/metrics_test.go | 34 + .../processor/batchprocessor/splittraces.go | 65 + .../batchprocessor/splittraces_test.go | 113 + .../batchprocessor/testdata/config.yaml | 19 + .../processor/cloningfanoutconnector.go | 138 + .../processor/cloningfanoutconnector_test.go | 155 + .../processor/fanoutconnector.go | 98 + .../processor/fanoutconnector_test.go | 204 + .../processor/filterprocessor/README.md | 107 + .../processor/filterprocessor/config.go | 39 + .../processor/filterprocessor/config_test.go | 313 + .../processor/filterprocessor/doc.go | 17 + .../processor/filterprocessor/expr_test.go | 207 + .../processor/filterprocessor/factory.go | 65 + .../processor/filterprocessor/factory_test.go | 94 + .../filterprocessor/filter_processor.go | 138 + .../filterprocessor/filter_processor_test.go | 413 ++ .../processor/filterprocessor/metric_index.go | 121 + .../filterprocessor/testdata/config_expr.yaml | 41 + .../testdata/config_invalid.yaml | 26 + .../testdata/config_regexp.yaml | 79 + .../testdata/config_strict.yaml | 51 + .../processor/memorylimiter/README.md | 107 + .../processor/memorylimiter/config.go | 56 + .../processor/memorylimiter/config_test.go | 66 + .../processor/memorylimiter/factory.go | 106 + .../processor/memorylimiter/factory_test.go | 80 + .../memorylimiter/internal/cgroups/cgroup.go | 94 + .../internal/cgroups/cgroup_test.go | 153 + .../memorylimiter/internal/cgroups/cgroups.go | 122 + .../internal/cgroups/cgroups_test.go | 137 + .../memorylimiter/internal/cgroups/doc.go | 40 + .../memorylimiter/internal/cgroups/errors.go | 67 + .../internal/cgroups/mountpoint.go | 182 + .../internal/cgroups/mountpoint_test.go | 199 + .../memorylimiter/internal/cgroups/subsys.go | 118 + .../internal/cgroups/subsys_test.go | 116 + .../testdata/cgroups/cpu/cpu.cfs_period_us | 1 + .../testdata/cgroups/cpu/cpu.cfs_quota_us | 1 + .../testdata/cgroups/empty/cpu.cfs_quota_us | 0 .../testdata/cgroups/invalid/cpu.cfs_quota_us | 1 + .../cgroups/undefined-period/cpu.cfs_quota_us | 1 + .../cgroups/undefined/cpu.cfs_period_us | 1 + .../cgroups/undefined/cpu.cfs_quota_us | 1 + .../cgroups/testdata/proc/cgroups/cgroup | 3 + .../cgroups/testdata/proc/cgroups/mountinfo | 8 + .../testdata/proc/invalid-cgroup/cgroup | 2 + .../testdata/proc/invalid-mountinfo/mountinfo | 1 + .../testdata/proc/untranslatable/cgroup | 2 + .../testdata/proc/untranslatable/mountinfo | 2 + .../internal/cgroups/util_test.go | 59 + .../internal/iruntime/total_memory_linux.go | 33 + .../iruntime/total_memory_linux_test.go | 30 + .../internal/iruntime/total_memory_other.go | 29 + .../iruntime/total_memory_other_test.go | 30 + .../processor/memorylimiter/memorylimiter.go | 339 + .../memorylimiter/memorylimiter_test.go | 410 ++ .../memorylimiter/testdata/config.yaml | 36 + internal/otel_collector/processor/metrics.go | 159 + .../otel_collector/processor/metrics_test.go | 44 + .../processor/processorhelper/attraction.go | 283 + .../processorhelper/attraction_test.go | 881 +++ .../processor/processorhelper/factory.go | 149 + .../processor/processorhelper/factory_test.go | 96 + .../processor/processorhelper/hasher.go | 72 + .../processor/processorhelper/processor.go | 256 + .../processorhelper/processor_test.go | 171 + .../processor/queuedprocessor/README.md | 3 + .../processor/queuedprocessor/config.go | 35 + .../processor/queuedprocessor/config_test.go | 56 + .../processor/queuedprocessor/factory.go | 72 + .../processor/queuedprocessor/factory_test.go | 47 + .../processor/queuedprocessor/metrics.go | 81 + .../queuedprocessor/queued_processor.go | 345 + .../queuedprocessor/queued_processor_test.go | 449 ++ .../queuedprocessor/testdata/config.yaml | 20 + .../processor/resourceprocessor/README.md | 28 + .../processor/resourceprocessor/config.go | 35 + .../resourceprocessor/config_test.go | 57 + .../processor/resourceprocessor/doc.go | 17 + .../processor/resourceprocessor/factory.go | 141 + .../resourceprocessor/factory_test.go | 117 + .../resourceprocessor/resource_processor.go | 61 + .../resource_processor_test.go | 247 + .../resourceprocessor/testdata/config.yaml | 41 + .../probabilisticsamplerprocessor/README.md | 33 + .../probabilisticsamplerprocessor/config.go | 29 + .../config_test.go | 67 + .../probabilisticsamplerprocessor/factory.go | 57 + .../factory_test.go | 41 + .../probabilisticsampler.go | 234 + .../probabilisticsampler_test.go | 506 ++ .../testdata/config.yaml | 35 + .../testdata/empty.yaml | 16 + .../processor/spanprocessor/README.md | 101 + .../processor/spanprocessor/config.go | 79 + .../processor/spanprocessor/config_test.go | 108 + .../processor/spanprocessor/doc.go | 17 + .../processor/spanprocessor/factory.go | 81 + .../processor/spanprocessor/factory_test.go | 107 + .../processor/spanprocessor/span.go | 221 + .../processor/spanprocessor/span_test.go | 599 ++ .../spanprocessor/testdata/config.yaml | 95 + internal/otel_collector/proto_patch.sed | 40 + internal/otel_collector/receiver/README.md | 83 + internal/otel_collector/receiver/doc.go | 22 + .../receiver/fluentforwardreceiver/README.md | 33 + .../receiver/fluentforwardreceiver/ack.go | 36 + .../fluentforwardreceiver/ack_test.go | 52 + .../fluentforwardreceiver/collector.go | 96 + .../receiver/fluentforwardreceiver/config.go | 29 + .../fluentforwardreceiver/config_test.go | 47 + .../fluentforwardreceiver/conversion.go | 412 ++ .../fluentforwardreceiver/conversion_test.go | 213 + .../receiver/fluentforwardreceiver/factory.go | 58 + .../fluentforwardreceiver/factory_test.go | 48 + .../fluentforwardreceiver/heartbeat.go | 48 + .../fluentforwardreceiver/heartbeat_test.go | 49 + .../fluentforwardreceiver/observ/metrics.go | 89 + .../observ/metrics_test.go | 25 + .../fluentforwardreceiver/parse_test.go | 53 + .../fluentforwardreceiver/receiver.go | 94 + .../fluentforwardreceiver/receiver_test.go | 438 ++ .../receiver/fluentforwardreceiver/server.go | 207 + .../fluentforwardreceiver/server_test.go | 127 + .../testdata/config.yaml | 15 + .../testdata/forward-event.hexdump | 12 + .../forward-packed-compressed.hexdump | 32 + .../testdata/forward-packed.hexdump | 28 + .../testdata/message-event.hexdump | 10 + .../receiver/fluentforwardreceiver/timeext.go | 54 + .../fluentforwardreceiver/timeext_test.go | 41 + .../receiver/hostmetricsreceiver/README.md | 118 + .../receiver/hostmetricsreceiver/codegen.go | 19 + .../receiver/hostmetricsreceiver/config.go | 26 + .../hostmetricsreceiver/config_test.go | 118 + .../hostmetricsreceiver/example_config.yaml | 29 + .../receiver/hostmetricsreceiver/factory.go | 223 + .../hostmetricsreceiver/factory_test.go | 66 + .../hostmetrics_receiver_test.go | 439 ++ .../internal/metadata/generated_metrics.go | 168 + .../internal/perfcounters/doc.go | 19 + .../perfcounters/perfcounter_scraper.go | 202 + .../perfcounters/perfcounter_scraper_mock.go | 167 + .../perfcounters/perfcounter_scraper_test.go | 171 + .../hostmetricsreceiver/internal/scraper.go | 55 + .../internal/scraper/cpuscraper/config.go | 22 + .../scraper/cpuscraper/cpu_scraper.go | 95 + .../scraper/cpuscraper/cpu_scraper_linux.go | 37 + .../scraper/cpuscraper/cpu_scraper_others.go | 33 + .../scraper/cpuscraper/cpu_scraper_test.go | 127 + .../internal/scraper/cpuscraper/factory.go | 58 + .../scraper/cpuscraper/factory_test.go | 39 + .../internal/scraper/diskscraper/config.go | 37 + .../scraper/diskscraper/disk_metadata.go | 107 + .../diskscraper/disk_scraper_others.go | 222 + .../disk_scraper_others_fallback.go | 28 + .../diskscraper/disk_scraper_others_linux.go | 40 + .../diskscraper/disk_scraper_others_test.go | 69 + .../scraper/diskscraper/disk_scraper_test.go | 164 + .../diskscraper/disk_scraper_windows.go | 214 + .../diskscraper/disk_scraper_windows_test.go | 79 + .../internal/scraper/diskscraper/factory.go | 61 + .../scraper/diskscraper/factory_test.go | 48 + .../scraper/filesystemscraper/config.go | 140 + .../scraper/filesystemscraper/factory.go | 62 + .../scraper/filesystemscraper/factory_test.go | 48 + .../filesystemscraper/filesystem_metadata.go | 63 + .../filesystemscraper/filesystem_scraper.go | 167 + .../filesystem_scraper_others.go | 33 + .../filesystem_scraper_test.go | 288 + .../filesystem_scraper_unix.go | 44 + .../internal/scraper/loadscraper/config.go | 22 + .../internal/scraper/loadscraper/factory.go | 59 + .../scraper/loadscraper/factory_test.go | 39 + .../scraper/loadscraper/load_metadata.go | 48 + .../scraper/loadscraper/load_scraper.go | 81 + .../loadscraper/load_scraper_others.go | 37 + .../scraper/loadscraper/load_scraper_test.go | 92 + .../loadscraper/load_scraper_windows.go | 169 + .../loadscraper/load_scraper_windows_test.go | 124 + .../internal/scraper/memoryscraper/config.go | 22 + .../internal/scraper/memoryscraper/factory.go | 54 + .../scraper/memoryscraper/factory_test.go | 39 + .../scraper/memoryscraper/memory_scraper.go | 72 + .../memoryscraper/memory_scraper_linux.go | 35 + .../memoryscraper/memory_scraper_others.go | 32 + .../memoryscraper/memory_scraper_test.go | 99 + .../memoryscraper/memory_scraper_windows.go | 31 + .../internal/scraper/networkscraper/config.go | 36 + .../scraper/networkscraper/factory.go | 61 + .../scraper/networkscraper/factory_test.go | 48 + .../networkscraper/network_metadata.go | 96 + .../scraper/networkscraper/network_others.go | 32 + .../scraper/networkscraper/network_scraper.go | 244 + .../networkscraper/network_scraper_test.go | 171 + .../scraper/networkscraper/network_windows.go | 32 + .../scraper/processesscraper/config.go | 22 + .../scraper/processesscraper/factory.go | 58 + .../scraper/processesscraper/factory_test.go | 39 + .../processesscraper/processes_metadata.go | 45 + .../processesscraper/processes_scraper.go | 59 + .../processes_scraper_fallback.go | 25 + .../processes_scraper_test.go | 98 + .../processes_scraper_unix.go | 49 + .../internal/scraper/processscraper/config.go | 37 + .../scraper/processscraper/factory.go | 67 + .../scraper/processscraper/factory_test.go | 45 + .../scraper/processscraper/process.go | 134 + .../processscraper/process_metadata.go | 91 + .../scraper/processscraper/process_scraper.go | 253 + .../processscraper/process_scraper_linux.go | 69 + .../processscraper/process_scraper_others.go | 36 + .../processscraper/process_scraper_test.go | 485 ++ .../processscraper/process_scraper_windows.go | 71 + .../internal/scraper/swapscraper/config.go | 22 + .../internal/scraper/swapscraper/factory.go | 58 + .../scraper/swapscraper/factory_test.go | 38 + .../scraper/swapscraper/pagefile_windows.go | 96 + .../scraper/swapscraper/swap_metadata.go | 86 + .../swapscraper/swap_scraper_others.go | 160 + .../swapscraper/swap_scraper_others_test.go | 86 + .../scraper/swapscraper/swap_scraper_test.go | 153 + .../swapscraper/swap_scraper_windows.go | 177 + .../swapscraper/swap_scraper_windows_test.go | 127 + .../hostmetricsreceiver/internal/testutils.go | 110 + .../hostmetricsreceiver/internal/utils.go | 25 + .../hostmetricsreceiver/metadata.yaml | 34 + .../testdata/config-invalidscraperkey.yaml | 18 + .../testdata/config-noscrapers.yaml | 15 + .../hostmetricsreceiver/testdata/config.yaml | 35 + .../receiver/jaegerreceiver/README.md | 96 + .../receiver/jaegerreceiver/config.go | 74 + .../receiver/jaegerreceiver/config_test.go | 185 + .../receiver/jaegerreceiver/errors.go | 24 + .../receiver/jaegerreceiver/factory.go | 255 + .../receiver/jaegerreceiver/factory_test.go | 352 + .../jaegerreceiver/jaeger_agent_test.go | 253 + .../testdata/bad_empty_config.yaml | 15 + .../testdata/bad_no_proto_config.yaml | 16 + .../testdata/bad_proto_config.yaml | 19 + .../bad_typo_default_proto_config.yaml | 20 + .../receiver/jaegerreceiver/testdata/ca.crt | 20 + .../jaegerreceiver/testdata/client.crt | 21 + .../jaegerreceiver/testdata/client.key | 27 + .../jaegerreceiver/testdata/config.yaml | 66 + .../jaegerreceiver/testdata/server.crt | 21 + .../jaegerreceiver/testdata/server.key | 27 + .../jaegerreceiver/testdata/strategies.json | 30 + .../receiver/jaegerreceiver/trace_receiver.go | 505 ++ .../jaegerreceiver/trace_receiver_test.go | 589 ++ .../receiver/kafkareceiver/README.md | 63 + .../receiver/kafkareceiver/config.go | 43 + .../receiver/kafkareceiver/config_test.go | 70 + .../receiver/kafkareceiver/factory.go | 107 + .../receiver/kafkareceiver/factory_test.go | 94 + .../kafkareceiver/jaeger_unmarshaller.go | 69 + .../kafkareceiver/jaeger_unmarshaller_test.go | 87 + .../receiver/kafkareceiver/kafka_receiver.go | 181 + .../kafkareceiver/kafka_receiver_test.go | 334 + .../receiver/kafkareceiver/metrics.go | 85 + .../receiver/kafkareceiver/metrics_test.go | 35 + .../kafkareceiver/otlp_unmarshaller.go | 38 + .../kafkareceiver/otlp_unmarshaller_test.go | 49 + .../kafkareceiver/testdata/config.yaml | 30 + .../receiver/kafkareceiver/unmarshaller.go | 46 + .../kafkareceiver/unmarshaller_test.go | 42 + .../kafkareceiver/zipkin_unmarshaller.go | 106 + .../kafkareceiver/zipkin_unmarshaller_test.go | 120 + .../receiver/opencensusreceiver/README.md | 53 + .../receiver/opencensusreceiver/config.go | 51 + .../opencensusreceiver/config_test.go | 196 + .../receiver/opencensusreceiver/factory.go | 121 + .../opencensusreceiver/factory_test.go | 201 + .../opencensusreceiver/ocmetrics/doc.go | 17 + .../ocmetrics/opencensus.go | 159 + .../ocmetrics/opencensus_test.go | 418 ++ .../opencensusreceiver/octrace/doc.go | 17 + .../octrace/observability_test.go | 189 + .../opencensusreceiver/octrace/opencensus.go | 166 + .../octrace/opencensus_test.go | 395 + .../opencensusreceiver/octrace/options.go | 20 + .../receiver/opencensusreceiver/opencensus.go | 275 + .../opencensusreceiver/opencensus_test.go | 620 ++ .../receiver/opencensusreceiver/options.go | 56 + .../opencensusreceiver/testdata/config.yaml | 69 + .../receiver/otlpreceiver/README.md | 67 + .../receiver/otlpreceiver/config.go | 34 + .../receiver/otlpreceiver/config_test.go | 218 + .../receiver/otlpreceiver/factory.go | 198 + .../receiver/otlpreceiver/factory_test.go | 354 + .../receiver/otlpreceiver/logs/otlp.go | 81 + .../receiver/otlpreceiver/logs/otlp_test.go | 181 + .../receiver/otlpreceiver/marshal_jsonpb.go | 301 + .../otlpreceiver/marshal_jsonpb_test.go | 103 + .../receiver/otlpreceiver/metrics/otlp.go | 79 + .../otlpreceiver/metrics/otlp_test.go | 222 + .../receiver/otlpreceiver/mixin.go | 67 + .../receiver/otlpreceiver/otlp.go | 232 + .../receiver/otlpreceiver/otlp_test.go | 777 ++ .../receiver/otlpreceiver/otlphttp.go | 76 + .../testdata/bad_empty_config.yaml | 15 + .../testdata/bad_no_proto_config.yaml | 16 + .../testdata/bad_proto_config.yaml | 18 + .../otlpreceiver/testdata/config.yaml | 95 + .../testdata/typo_default_proto_config.yaml | 18 + .../receiver/otlpreceiver/trace/otlp.go | 107 + .../receiver/otlpreceiver/trace/otlp_test.go | 288 + .../receiver/prometheusreceiver/DESIGN.md | 584 ++ .../receiver/prometheusreceiver/README.md | 62 + .../receiver/prometheusreceiver/config.go | 38 + .../prometheusreceiver/config_test.go | 146 + .../receiver/prometheusreceiver/doc.go | 17 + .../receiver/prometheusreceiver/factory.go | 107 + .../prometheusreceiver/factory_test.go | 57 + .../internal/internal_test.go | 55 + .../prometheusreceiver/internal/logger.go | 136 + .../internal/logger_test.go | 197 + .../prometheusreceiver/internal/metadata.go | 66 + .../internal/metricfamily.go | 373 + .../internal/metrics_adjuster.go | 371 + .../internal/metrics_adjuster_test.go | 377 + .../internal/metricsbuilder.go | 303 + .../internal/metricsbuilder_test.go | 1380 ++++ .../prometheusreceiver/internal/ocastore.go | 109 + .../internal/ocastore_test.go | 61 + .../internal/transaction.go | 236 + .../internal/transaction_test.go | 163 + .../prometheusreceiver/metrics_receiver.go | 96 + .../metrics_receiver_test.go | 1183 +++ .../scrapeloop-flowchart.png | Bin 0 -> 43026 bytes .../prometheusreceiver/testdata/config.yaml | 24 + .../testdata/config_env.yaml | 19 + .../testdata/config_k8s.yaml | 42 + .../testdata/config_sd.yaml | 85 + .../invalid-config-prometheus-section.yaml | 20 + .../testdata/invalid-config-section.yaml | 20 + .../receiver/receiverhelper/factory.go | 157 + .../receiver/receiverhelper/factory_test.go | 96 + .../receiver/scraperhelper/errors.go | 59 + .../receiver/scraperhelper/errors_test.go | 88 + .../receiver/scraperhelper/scraper.go | 168 + .../scraperhelper/scrapercontroller.go | 276 + .../scraperhelper/scrapercontroller_test.go | 477 ++ .../receiver/zipkinreceiver/README.md | 29 + .../receiver/zipkinreceiver/config.go | 32 + .../receiver/zipkinreceiver/config_test.go | 70 + .../receiver/zipkinreceiver/factory.go | 68 + .../receiver/zipkinreceiver/factory_test.go | 53 + .../zipkinreceiver/proto_parse_test.go | 305 + .../zipkinreceiver/testdata/config.yaml | 20 + .../zipkinreceiver/testdata/sample1.json | 288 + .../zipkinreceiver/testdata/sample2.json | 32 + .../receiver/zipkinreceiver/trace_receiver.go | 309 + .../zipkinreceiver/trace_receiver_test.go | 601 ++ .../otel_collector/service/builder/builder.go | 57 + .../otel_collector/service/builder/doc.go | 17 + .../service/builder/exporters_builder.go | 320 + .../service/builder/exporters_builder_test.go | 274 + .../service/builder/extensions_builder.go | 181 + .../service/builder/pipelines_builder.go | 266 + .../service/builder/pipelines_builder_test.go | 309 + .../service/builder/receivers_builder.go | 352 + .../service/builder/receivers_builder_test.go | 396 + .../testdata/bad_processor_factory.yaml | 23 + .../testdata/bad_receiver_factory.yaml | 16 + .../builder/testdata/pipelines_builder.yaml | 40 + .../builder/testdata/unused_receiver.yaml | 12 + .../service/defaultcomponents/defaults.go | 26 + .../defaultcomponents/defaults_test.go | 105 + .../service/defaultcomponents/docs_test.go | 51 + .../otel_collector/service/internal/gen.go | 18 + .../service/internal/resources.go | 323 + .../internal/telemetry/process_telemetry.go | 183 + .../telemetry/process_telemetry_test.go | 77 + .../service/internal/templates.go | 152 + .../internal/templates/component_header.html | 7 + .../internal/templates/extensions_table.html | 11 + .../service/internal/templates/footer.html | 2 + .../service/internal/templates/header.html | 11 + .../internal/templates/pipelines_table.html | 45 + .../internal/templates/properties_table.html | 14 + .../service/internal/templates_test.go | 87 + internal/otel_collector/service/logger.go | 77 + internal/otel_collector/service/service.go | 596 ++ .../otel_collector/service/service_test.go | 649 ++ .../otel_collector/service/service_windows.go | 147 + .../service/service_windows_test.go | 57 + internal/otel_collector/service/set_flag.go | 98 + .../otel_collector/service/set_flag_test.go | 64 + internal/otel_collector/service/telemetry.go | 144 + .../testdata/otelcol-config-minimal.yaml | 15 + .../service/testdata/otelcol-config.yaml | 31 + .../otel_collector/testbed/CCRepo_result.png | Bin 0 -> 2052738 bytes internal/otel_collector/testbed/README.md | 136 + .../testbed/correctness/.gitignore | 2 + .../metrics/correctness_test_case.go | 111 + .../testbed/correctness/metrics/doc.go | 36 + .../correctness/metrics/metric_diff.go | 332 + .../correctness/metrics/metric_diff_test.go | 103 + .../correctness/metrics/metric_index.go | 52 + .../correctness/metrics/metric_supplier.go | 37 + .../metrics/metrics_correctness_test.go | 92 + .../metrics/metrics_test_harness.go | 107 + .../correctness/metrics/results_dir.go | 41 + .../generated_pict_pairs_metrics_pipeline.txt | 2 + .../testdata/pict_input_metrics_pipeline.txt | 2 + .../testbed/correctness/traces/.gitignore | 2 + .../correctness/traces/correctness_test.go | 106 + .../testbed/correctness/traces/runtests.sh | 35 + .../generated_pict_pairs_traces_pipeline.txt | 17 + .../testdata/pict_input_traces_pipeline.txt | 2 + .../testbed/correctness/utils.go | 175 + .../testbed/correctness_result.png | Bin 0 -> 2161669 bytes .../otel_collector/testbed/e2e_diagram.jpeg | Bin 0 -> 53309 bytes .../otel_collector/testbed/testbed/.gitignore | 1 + .../testbed/testbed/child_process.go | 512 ++ .../testbed/testbed/data_providers.go | 310 + .../testbed/testbed/data_providers_test.go | 40 + .../testbed/testbed/load_generator.go | 242 + .../testbed/testbed/mock_backend.go | 242 + .../testbed/testbed/mock_backend_test.go | 101 + .../testbed/testbed/mockconsumer.go | 27 + .../otel_collector/testbed/testbed/options.go | 42 + .../testbed/testbed/otelcol_runner.go | 178 + .../testbed/testbed/otelcol_runner_test.go | 65 + .../testbed/testbed/receivers.go | 383 + .../otel_collector/testbed/testbed/results.go | 215 + .../otel_collector/testbed/testbed/senders.go | 746 ++ .../testbed/testbed/test_bed.go | 89 + .../testbed/testbed/test_case.go | 355 + .../otel_collector/testbed/testbed/utils.go | 25 + .../testbed/testbed/validator.go | 618 ++ .../otel_collector/testbed/tests/.gitignore | 2 + .../otel_collector/testbed/tests/e2e_test.go | 91 + .../otel_collector/testbed/tests/log_test.go | 84 + .../testbed/tests/metric_test.go | 98 + .../testbed/tests/resource_processor_test.go | 291 + .../testbed/tests/results/BASELINE.md | 25 + .../otel_collector/testbed/tests/runtests.sh | 34 + .../otel_collector/testbed/tests/scenarios.go | 333 + .../testbed/tests/testdata/agent-config.yaml | 27 + .../tests/testdata/memory-limiter.yaml | 30 + .../testbed/tests/trace_test.go | 456 ++ .../otel_collector/testbed/tests/utils.go | 25 + .../otel_collector/testutil/logstest/logs.go | 52 + .../testutil/logstest/logs_test.go | 35 + .../testutil/metricstestutil/metricsutil.go | 204 + .../metricstestutil/metricsutil_test.go | 180 + internal/otel_collector/testutil/testutil.go | 235 + .../otel_collector/testutil/testutil_test.go | 92 + .../translator/conventions/opencensus.go | 30 + .../translator/conventions/opentelemetry.go | 278 + .../translator/internaldata/metrics_to_oc.go | 523 ++ .../internaldata/metrics_to_oc_test.go | 184 + .../internaldata/oc_testdata_test.go | 542 ++ .../translator/internaldata/oc_to_metrics.go | 434 ++ .../internaldata/oc_to_metrics_test.go | 243 + .../translator/internaldata/oc_to_resource.go | 132 + .../internaldata/oc_to_resource_test.go | 102 + .../translator/internaldata/oc_to_traces.go | 395 + .../internaldata/oc_to_traces_test.go | 485 ++ .../translator/internaldata/resource_to_oc.go | 172 + .../internaldata/resource_to_oc_test.go | 288 + .../translator/internaldata/traces_to_oc.go | 414 ++ .../internaldata/traces_to_oc_test.go | 446 ++ .../otel_collector/translator/trace/README.md | 74 + .../translator/trace/big_endian_converter.go | 107 + .../trace/big_endian_converter_test.go | 126 + .../translator/trace/grpc_http_mapper.go | 98 + .../translator/trace/grpc_http_mapper_test.go | 35 + .../translator/trace/jaeger/constants.go | 24 + .../trace/jaeger/jaegerproto_to_traces.go | 377 + .../jaeger/jaegerproto_to_traces_test.go | 865 +++ .../trace/jaeger/jaegerthrift_to_traces.go | 201 + .../jaeger/jaegerthrift_to_traces_test.go | 301 + .../jaeger/testdata/jaegerproto_batch_01.json | 202 + .../jaeger/testdata/jaegerproto_batch_02.json | 80 + .../jaeger/testdata/ocproto_batch_01.json | 139 + .../jaeger/testdata/ocproto_batch_02.json | 69 + .../proto_batch_no_binary_tags_01.json | 149 + .../proto_batch_no_binary_tags_02.json | 65 + .../jaeger/testdata/thrift_batch_01.json | 116 + .../jaeger/testdata/thrift_batch_02.json | 44 + .../trace/jaeger/traces_to_jaegerproto.go | 446 ++ .../jaeger/traces_to_jaegerproto_test.go | 371 + .../translator/trace/opencensus_helper.go | 29 + .../translator/trace/protospan_translation.go | 307 + .../trace/protospan_translation_test.go | 190 + .../translator/trace/zipkin/attributekeys.go | 29 + .../translator/trace/zipkin/status_code.go | 197 + .../trace/zipkin/status_code_test.go | 277 + .../testdata/zipkin_v1_error_batch.json | 64 + .../testdata/zipkin_v1_local_component.json | 37 + .../testdata/zipkin_v1_multiple_batches.json | 154 + .../testdata/zipkin_v1_single_batch.json | 144 + .../zipkin_v1_thrift_local_component.json | 39 + .../zipkin_v1_thrift_single_batch.json | 148 + .../zipkin/testdata/zipkin_v2_single.json | 38 + .../trace/zipkin/traces_to_zipkinv2.go | 351 + .../trace/zipkin/traces_to_zipkinv2_test.go | 153 + .../zipkin/zipkinv1_thrift_to_protospan.go | 267 + .../zipkinv1_thrift_to_protospan_test.go | 619 ++ .../trace/zipkin/zipkinv1_thrift_to_traces.go | 34 + .../zipkin/zipkinv1_thrift_to_traces_test.go | 39 + .../trace/zipkin/zipkinv1_to_protospan.go | 517 ++ .../zipkin/zipkinv1_to_protospan_test.go | 790 ++ .../trace/zipkin/zipkinv1_to_traces.go | 35 + .../trace/zipkin/zipkinv1_to_traces_test.go | 42 + .../trace/zipkin/zipkinv2_to_traces.go | 427 ++ .../trace/zipkin/zipkinv2_to_traces_test.go | 130 + 1028 files changed, 149913 insertions(+), 1 deletion(-) create mode 100644 internal/otel_collector/CHANGELOG.md create mode 100644 internal/otel_collector/CONTRIBUTING.md create mode 100644 internal/otel_collector/LICENSE create mode 100644 internal/otel_collector/Makefile create mode 100644 internal/otel_collector/Makefile.Common create mode 100644 internal/otel_collector/README.md create mode 100644 internal/otel_collector/client/client.go create mode 100644 internal/otel_collector/client/client_test.go create mode 100644 internal/otel_collector/cmd/otelcol/Dockerfile create mode 100644 internal/otel_collector/cmd/otelcol/config.yaml create mode 100644 internal/otel_collector/cmd/otelcol/main.go create mode 100644 internal/otel_collector/cmd/otelcol/main_others.go create mode 100644 internal/otel_collector/cmd/otelcol/main_windows.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/base_fields.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/base_slices.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/base_structs.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/common_structs.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/files.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/log_structs.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/metrics_structs.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/resource_structs.go create mode 100644 internal/otel_collector/cmd/pdatagen/internal/trace_structs.go create mode 100644 internal/otel_collector/cmd/pdatagen/main.go create mode 100644 internal/otel_collector/component/component.go create mode 100644 internal/otel_collector/component/component_test.go create mode 100644 internal/otel_collector/component/componenterror/errors.go create mode 100644 internal/otel_collector/component/componenterror/errors_test.go create mode 100644 internal/otel_collector/component/componenthelper/component.go create mode 100644 internal/otel_collector/component/componenthelper/component_test.go create mode 100644 internal/otel_collector/component/componenttest/application_start_info.go create mode 100644 internal/otel_collector/component/componenttest/doc.go create mode 100644 internal/otel_collector/component/componenttest/docs.go create mode 100644 internal/otel_collector/component/componenttest/docs_test.go create mode 100644 internal/otel_collector/component/componenttest/error_waiting_host.go create mode 100644 internal/otel_collector/component/componenttest/error_waiting_host_test.go create mode 100644 internal/otel_collector/component/componenttest/example_factories.go create mode 100644 internal/otel_collector/component/componenttest/example_factories_test.go create mode 100644 internal/otel_collector/component/componenttest/nop_host.go create mode 100644 internal/otel_collector/component/componenttest/nop_host_test.go create mode 100644 internal/otel_collector/component/componenttest/testdata/invalid_go.txt create mode 100644 internal/otel_collector/component/componenttest/testdata/valid_go.txt create mode 100644 internal/otel_collector/component/exporter.go create mode 100644 internal/otel_collector/component/exporter_test.go create mode 100644 internal/otel_collector/component/extension.go create mode 100644 internal/otel_collector/component/factories.go create mode 100644 internal/otel_collector/component/processor.go create mode 100644 internal/otel_collector/component/processor_test.go create mode 100644 internal/otel_collector/component/receiver.go create mode 100644 internal/otel_collector/component/receiver_test.go create mode 100644 internal/otel_collector/config/config.go create mode 100644 internal/otel_collector/config/config_test.go create mode 100644 internal/otel_collector/config/configauth/README.md create mode 100644 internal/otel_collector/config/configauth/authenticator.go create mode 100644 internal/otel_collector/config/configauth/authenticator_test.go create mode 100644 internal/otel_collector/config/configauth/configauth.go create mode 100644 internal/otel_collector/config/configauth/configauth_test.go create mode 100644 internal/otel_collector/config/configauth/context.go create mode 100644 internal/otel_collector/config/configauth/context_test.go create mode 100644 internal/otel_collector/config/configauth/oidc_authenticator.go create mode 100644 internal/otel_collector/config/configauth/oidc_authenticator_test.go create mode 100644 internal/otel_collector/config/configauth/oidc_server_test.go create mode 100644 internal/otel_collector/config/configcheck/configcheck.go create mode 100644 internal/otel_collector/config/configcheck/configcheck_test.go create mode 100644 internal/otel_collector/config/configerror/configerror.go create mode 100644 internal/otel_collector/config/configgrpc/README.md create mode 100644 internal/otel_collector/config/configgrpc/bearer_token.go create mode 100644 internal/otel_collector/config/configgrpc/bearer_token_test.go create mode 100644 internal/otel_collector/config/configgrpc/configgrpc.go create mode 100644 internal/otel_collector/config/configgrpc/configgrpc_test.go create mode 100644 internal/otel_collector/config/configgrpc/gzip.go create mode 100644 internal/otel_collector/config/configgrpc/testdata/ca.crt create mode 100644 internal/otel_collector/config/configgrpc/testdata/client.crt create mode 100644 internal/otel_collector/config/configgrpc/testdata/client.key create mode 100644 internal/otel_collector/config/configgrpc/testdata/server.crt create mode 100644 internal/otel_collector/config/configgrpc/testdata/server.key create mode 100644 internal/otel_collector/config/confighttp/README.md create mode 100644 internal/otel_collector/config/confighttp/confighttp.go create mode 100644 internal/otel_collector/config/confighttp/confighttp_test.go create mode 100644 internal/otel_collector/config/confighttp/testdata/ca.crt create mode 100644 internal/otel_collector/config/confighttp/testdata/client.crt create mode 100644 internal/otel_collector/config/confighttp/testdata/client.key create mode 100644 internal/otel_collector/config/confighttp/testdata/server.crt create mode 100644 internal/otel_collector/config/confighttp/testdata/server.key create mode 100644 internal/otel_collector/config/configmodels/configmodels.go create mode 100644 internal/otel_collector/config/confignet/README.md create mode 100644 internal/otel_collector/config/confignet/confignet.go create mode 100644 internal/otel_collector/config/confignet/confignet_test.go create mode 100644 internal/otel_collector/config/configtelemetry/configtelemetry.go create mode 100644 internal/otel_collector/config/configtelemetry/configtelemetry_test.go create mode 100644 internal/otel_collector/config/configtest/configtest.go create mode 100644 internal/otel_collector/config/configtest/configtest_test.go create mode 100644 internal/otel_collector/config/configtest/testdata/config.yaml create mode 100644 internal/otel_collector/config/configtls/README.md create mode 100644 internal/otel_collector/config/configtls/configtls.go create mode 100644 internal/otel_collector/config/configtls/configtls_test.go create mode 100644 internal/otel_collector/config/configtls/testdata/test-cert.pem create mode 100644 internal/otel_collector/config/configtls/testdata/test-key.pem create mode 100644 internal/otel_collector/config/configtls/testdata/testCA-bad.txt create mode 100644 internal/otel_collector/config/configtls/testdata/testCA.pem create mode 100644 internal/otel_collector/config/testdata/duplicate-exporter.yaml create mode 100644 internal/otel_collector/config/testdata/duplicate-extension.yaml create mode 100644 internal/otel_collector/config/testdata/duplicate-pipeline.yaml create mode 100644 internal/otel_collector/config/testdata/duplicate-processor.yaml create mode 100644 internal/otel_collector/config/testdata/duplicate-receiver.yaml create mode 100644 internal/otel_collector/config/testdata/empty-config.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-exporter-section.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-exporter-sub-config.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-extension-name.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-extension-section.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-extension-sub-config.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-pipeline-section.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-pipeline-sub-config.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-pipeline-type-and-name.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-pipeline-type.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-processor-section.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-processor-sub-config.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-receiver-name.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-receiver-reference.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-receiver-section.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-receiver-sub-config.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-sequence-value.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-service-extensions-value.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-service-section.yaml create mode 100644 internal/otel_collector/config/testdata/invalid-top-level-section.yaml create mode 100644 internal/otel_collector/config/testdata/metric-pipeline-cannot-have-processors.yaml create mode 100644 internal/otel_collector/config/testdata/missing-all-sections.yaml create mode 100644 internal/otel_collector/config/testdata/missing-exporter-name-after-slash.yaml create mode 100644 internal/otel_collector/config/testdata/missing-exporters.yaml create mode 100644 internal/otel_collector/config/testdata/missing-extension-type.yaml create mode 100644 internal/otel_collector/config/testdata/missing-pipelines.yaml create mode 100644 internal/otel_collector/config/testdata/missing-processor-type.yaml create mode 100644 internal/otel_collector/config/testdata/missing-processors.yaml create mode 100644 internal/otel_collector/config/testdata/missing-receiver-type.yaml create mode 100644 internal/otel_collector/config/testdata/missing-receivers.yaml create mode 100644 internal/otel_collector/config/testdata/multiproto-config.yaml create mode 100644 internal/otel_collector/config/testdata/pipeline-exporter-not-exists.yaml create mode 100644 internal/otel_collector/config/testdata/pipeline-must-have-exporter.yaml create mode 100644 internal/otel_collector/config/testdata/pipeline-must-have-exporter2.yaml create mode 100644 internal/otel_collector/config/testdata/pipeline-must-have-receiver.yaml create mode 100644 internal/otel_collector/config/testdata/pipeline-must-have-receiver2.yaml create mode 100644 internal/otel_collector/config/testdata/pipeline-processor-not-exists.yaml create mode 100644 internal/otel_collector/config/testdata/simple-config-with-all-env.yaml create mode 100644 internal/otel_collector/config/testdata/simple-config-with-escaped-env.yaml create mode 100644 internal/otel_collector/config/testdata/simple-config-with-no-env.yaml create mode 100644 internal/otel_collector/config/testdata/simple-config-with-partial-env.yaml create mode 100644 internal/otel_collector/config/testdata/unknown-exporter-type.yaml create mode 100644 internal/otel_collector/config/testdata/unknown-extension-type.yaml create mode 100644 internal/otel_collector/config/testdata/unknown-processor-type.yaml create mode 100644 internal/otel_collector/config/testdata/unknown-receiver-type.yaml create mode 100644 internal/otel_collector/config/testdata/valid-config.yaml create mode 100644 internal/otel_collector/consumer/consumer.go create mode 100644 internal/otel_collector/consumer/consumerdata/consumerdata.go create mode 100644 internal/otel_collector/consumer/consumererror/partialerror.go create mode 100644 internal/otel_collector/consumer/consumererror/partialerror_test.go create mode 100644 internal/otel_collector/consumer/consumererror/partialscrapeerror.go create mode 100644 internal/otel_collector/consumer/consumererror/partialscrapeerror_test.go create mode 100644 internal/otel_collector/consumer/consumererror/permanenterror.go create mode 100644 internal/otel_collector/consumer/consumererror/permanenterror_test.go create mode 100644 internal/otel_collector/consumer/consumertest/nop.go create mode 100644 internal/otel_collector/consumer/consumertest/nop_test.go create mode 100644 internal/otel_collector/consumer/consumertest/sink.go create mode 100644 internal/otel_collector/consumer/consumertest/sink_test.go create mode 100644 internal/otel_collector/consumer/pdata/common.go create mode 100644 internal/otel_collector/consumer/pdata/common_test.go create mode 100644 internal/otel_collector/consumer/pdata/doc.go create mode 100644 internal/otel_collector/consumer/pdata/generated_common.go create mode 100644 internal/otel_collector/consumer/pdata/generated_common_test.go create mode 100644 internal/otel_collector/consumer/pdata/generated_log.go create mode 100644 internal/otel_collector/consumer/pdata/generated_log_test.go create mode 100644 internal/otel_collector/consumer/pdata/generated_metrics.go create mode 100644 internal/otel_collector/consumer/pdata/generated_metrics_test.go create mode 100644 internal/otel_collector/consumer/pdata/generated_resource.go create mode 100644 internal/otel_collector/consumer/pdata/generated_resource_test.go create mode 100644 internal/otel_collector/consumer/pdata/generated_trace.go create mode 100644 internal/otel_collector/consumer/pdata/generated_trace_test.go create mode 100644 internal/otel_collector/consumer/pdata/log.go create mode 100644 internal/otel_collector/consumer/pdata/log_test.go create mode 100644 internal/otel_collector/consumer/pdata/metric.go create mode 100644 internal/otel_collector/consumer/pdata/metric_test.go create mode 100644 internal/otel_collector/consumer/pdata/spanid.go create mode 100644 internal/otel_collector/consumer/pdata/timestamp.go create mode 100644 internal/otel_collector/consumer/pdata/timestamp_test.go create mode 100644 internal/otel_collector/consumer/pdata/trace.go create mode 100644 internal/otel_collector/consumer/pdata/trace_test.go create mode 100644 internal/otel_collector/consumer/pdata/traceid.go create mode 100644 internal/otel_collector/consumer/simple/metrics.go create mode 100644 internal/otel_collector/consumer/simple/metrics_test.go create mode 100644 internal/otel_collector/docs/design.md create mode 100644 internal/otel_collector/docs/images/design-exporters.png create mode 100644 internal/otel_collector/docs/images/design-pipelines.png create mode 100644 internal/otel_collector/docs/images/design-processors.png create mode 100644 internal/otel_collector/docs/images/design-receivers.png create mode 100644 internal/otel_collector/docs/images/design-service-lifecycle.png create mode 100644 internal/otel_collector/docs/images/opentelemetry-service-deployment-models.png create mode 100644 internal/otel_collector/docs/images/zpages-example.png create mode 100644 internal/otel_collector/docs/metric-metadata.md create mode 100644 internal/otel_collector/docs/metric-metadata.yaml create mode 100644 internal/otel_collector/docs/migrating-from-opencensus.md create mode 100644 internal/otel_collector/docs/monitoring.md create mode 100644 internal/otel_collector/docs/observability.md create mode 100644 internal/otel_collector/docs/performance.md create mode 100644 internal/otel_collector/docs/release.md create mode 100644 internal/otel_collector/docs/roadmap.md create mode 100644 internal/otel_collector/docs/service-extensions.md create mode 100644 internal/otel_collector/docs/troubleshooting.md create mode 100644 internal/otel_collector/docs/vision.md create mode 100644 internal/otel_collector/examples/README.md create mode 100644 internal/otel_collector/examples/demo/.env create mode 100644 internal/otel_collector/examples/demo/README.md create mode 100644 internal/otel_collector/examples/demo/docker-compose.yaml create mode 100644 internal/otel_collector/examples/demo/otel-agent-config.yaml create mode 100644 internal/otel_collector/examples/demo/otel-collector-config.yaml create mode 100644 internal/otel_collector/examples/demo/prometheus.yaml create mode 100644 internal/otel_collector/examples/k8s/otel-config.yaml create mode 100644 internal/otel_collector/examples/local/otel-config.yaml create mode 100644 internal/otel_collector/exporter/README.md create mode 100644 internal/otel_collector/exporter/doc.go create mode 100644 internal/otel_collector/exporter/exporterhelper/README.md create mode 100644 internal/otel_collector/exporter/exporterhelper/common.go create mode 100644 internal/otel_collector/exporter/exporterhelper/common_test.go create mode 100644 internal/otel_collector/exporter/exporterhelper/constants.go create mode 100644 internal/otel_collector/exporter/exporterhelper/factory.go create mode 100644 internal/otel_collector/exporter/exporterhelper/factory_test.go create mode 100644 internal/otel_collector/exporter/exporterhelper/logshelper.go create mode 100644 internal/otel_collector/exporter/exporterhelper/logshelper_test.go create mode 100644 internal/otel_collector/exporter/exporterhelper/metricshelper.go create mode 100644 internal/otel_collector/exporter/exporterhelper/metricshelper_test.go create mode 100644 internal/otel_collector/exporter/exporterhelper/queued_retry.go create mode 100644 internal/otel_collector/exporter/exporterhelper/queued_retry_test.go create mode 100644 internal/otel_collector/exporter/exporterhelper/resource_to_label.go create mode 100644 internal/otel_collector/exporter/exporterhelper/resource_to_label_test.go create mode 100644 internal/otel_collector/exporter/exporterhelper/tracehelper.go create mode 100644 internal/otel_collector/exporter/exporterhelper/tracehelper_test.go create mode 100644 internal/otel_collector/exporter/fileexporter/README.md create mode 100644 internal/otel_collector/exporter/fileexporter/config.go create mode 100644 internal/otel_collector/exporter/fileexporter/config_test.go create mode 100644 internal/otel_collector/exporter/fileexporter/factory.go create mode 100644 internal/otel_collector/exporter/fileexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/fileexporter/file_exporter.go create mode 100644 internal/otel_collector/exporter/fileexporter/file_exporter_test.go create mode 100644 internal/otel_collector/exporter/fileexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/jaegerexporter/README.md create mode 100644 internal/otel_collector/exporter/jaegerexporter/config.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/config_test.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/doc.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/exporter.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/exporter_test.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/factory.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/ca.crt create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/client.crt create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/client.key create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/server.crt create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/server.key create mode 100644 internal/otel_collector/exporter/jaegerexporter/testdata/test_cert.pem create mode 100644 internal/otel_collector/exporter/kafkaexporter/README.md create mode 100644 internal/otel_collector/exporter/kafkaexporter/authentication.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/authentication_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/config.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/config_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/factory.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/kafka_exporter.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/kafka_exporter_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/marshaller.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/marshaller_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/otlp_marshaller.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/otlp_marshaller_test.go create mode 100644 internal/otel_collector/exporter/kafkaexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/loggingexporter/README.md create mode 100644 internal/otel_collector/exporter/loggingexporter/config.go create mode 100644 internal/otel_collector/exporter/loggingexporter/config_test.go create mode 100644 internal/otel_collector/exporter/loggingexporter/factory.go create mode 100644 internal/otel_collector/exporter/loggingexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/loggingexporter/logging_exporter.go create mode 100644 internal/otel_collector/exporter/loggingexporter/logging_exporter_test.go create mode 100644 internal/otel_collector/exporter/loggingexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/opencensusexporter/README.md create mode 100644 internal/otel_collector/exporter/opencensusexporter/config.go create mode 100644 internal/otel_collector/exporter/opencensusexporter/config_test.go create mode 100644 internal/otel_collector/exporter/opencensusexporter/factory.go create mode 100644 internal/otel_collector/exporter/opencensusexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/opencensusexporter/opencensus.go create mode 100644 internal/otel_collector/exporter/opencensusexporter/opencensus_test.go create mode 100644 internal/otel_collector/exporter/opencensusexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/opencensusexporter/testdata/test_cert.pem create mode 100644 internal/otel_collector/exporter/otlpexporter/README.md create mode 100644 internal/otel_collector/exporter/otlpexporter/config.go create mode 100644 internal/otel_collector/exporter/otlpexporter/config_test.go create mode 100644 internal/otel_collector/exporter/otlpexporter/factory.go create mode 100644 internal/otel_collector/exporter/otlpexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/otlpexporter/otlp.go create mode 100644 internal/otel_collector/exporter/otlpexporter/otlp_test.go create mode 100644 internal/otel_collector/exporter/otlpexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/otlpexporter/testdata/test_cert.pem create mode 100644 internal/otel_collector/exporter/otlphttpexporter/README.md create mode 100644 internal/otel_collector/exporter/otlphttpexporter/config.go create mode 100644 internal/otel_collector/exporter/otlphttpexporter/config_test.go create mode 100644 internal/otel_collector/exporter/otlphttpexporter/factory.go create mode 100644 internal/otel_collector/exporter/otlphttpexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/otlphttpexporter/otlp.go create mode 100644 internal/otel_collector/exporter/otlphttpexporter/otlp_test.go create mode 100644 internal/otel_collector/exporter/otlphttpexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/otlphttpexporter/testdata/test_cert.pem create mode 100644 internal/otel_collector/exporter/prometheusexporter/README.md create mode 100644 internal/otel_collector/exporter/prometheusexporter/config.go create mode 100644 internal/otel_collector/exporter/prometheusexporter/config_test.go create mode 100644 internal/otel_collector/exporter/prometheusexporter/factory.go create mode 100644 internal/otel_collector/exporter/prometheusexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/prometheusexporter/prometheus.go create mode 100644 internal/otel_collector/exporter/prometheusexporter/prometheus_test.go create mode 100644 internal/otel_collector/exporter/prometheusexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/DESIGN.md create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/README.md create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/config.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/config_test.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/exporter.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/exporter_test.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/factory.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/helper.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/helper_test.go create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/img/cortex.png create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/img/timeseries.png create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/prometheusremotewriteexporter/testutil_test.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/README.md create mode 100644 internal/otel_collector/exporter/zipkinexporter/config.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/config_test.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/factory.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/factory_test.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/testdata/config.yaml create mode 100644 internal/otel_collector/exporter/zipkinexporter/testutils_test.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/zipkin.go create mode 100644 internal/otel_collector/exporter/zipkinexporter/zipkin_test.go create mode 100644 internal/otel_collector/extension/README.md create mode 100644 internal/otel_collector/extension/extensionhelper/factory.go create mode 100644 internal/otel_collector/extension/extensionhelper/factory_test.go create mode 100644 internal/otel_collector/extension/fluentbitextension/README.md create mode 100644 internal/otel_collector/extension/fluentbitextension/config.go create mode 100644 internal/otel_collector/extension/fluentbitextension/config_test.go create mode 100644 internal/otel_collector/extension/fluentbitextension/factory.go create mode 100644 internal/otel_collector/extension/fluentbitextension/factory_test.go create mode 100644 internal/otel_collector/extension/fluentbitextension/process.go create mode 100644 internal/otel_collector/extension/fluentbitextension/process_linux.go create mode 100644 internal/otel_collector/extension/fluentbitextension/process_linux_test.go create mode 100644 internal/otel_collector/extension/fluentbitextension/process_others.go create mode 100644 internal/otel_collector/extension/fluentbitextension/testdata/config.yaml create mode 100644 internal/otel_collector/extension/healthcheckextension/README.md create mode 100644 internal/otel_collector/extension/healthcheckextension/config.go create mode 100644 internal/otel_collector/extension/healthcheckextension/config_test.go create mode 100644 internal/otel_collector/extension/healthcheckextension/doc.go create mode 100644 internal/otel_collector/extension/healthcheckextension/factory.go create mode 100644 internal/otel_collector/extension/healthcheckextension/factory_test.go create mode 100644 internal/otel_collector/extension/healthcheckextension/healthcheckextension.go create mode 100644 internal/otel_collector/extension/healthcheckextension/healthcheckextension_test.go create mode 100644 internal/otel_collector/extension/healthcheckextension/testdata/config.yaml create mode 100644 internal/otel_collector/extension/pprofextension/README.md create mode 100644 internal/otel_collector/extension/pprofextension/config.go create mode 100644 internal/otel_collector/extension/pprofextension/config_test.go create mode 100644 internal/otel_collector/extension/pprofextension/doc.go create mode 100644 internal/otel_collector/extension/pprofextension/factory.go create mode 100644 internal/otel_collector/extension/pprofextension/factory_test.go create mode 100644 internal/otel_collector/extension/pprofextension/pprofextension.go create mode 100644 internal/otel_collector/extension/pprofextension/pprofextension_test.go create mode 100644 internal/otel_collector/extension/pprofextension/testdata/config.yaml create mode 100644 internal/otel_collector/extension/zpagesextension/README.md create mode 100644 internal/otel_collector/extension/zpagesextension/config.go create mode 100644 internal/otel_collector/extension/zpagesextension/config_test.go create mode 100644 internal/otel_collector/extension/zpagesextension/doc.go create mode 100644 internal/otel_collector/extension/zpagesextension/factory.go create mode 100644 internal/otel_collector/extension/zpagesextension/factory_test.go create mode 100644 internal/otel_collector/extension/zpagesextension/testdata/config.yaml create mode 100644 internal/otel_collector/extension/zpagesextension/zpagesextension.go create mode 100644 internal/otel_collector/extension/zpagesextension/zpagesextension_test.go create mode 100644 internal/otel_collector/go.mod create mode 100644 internal/otel_collector/go.sum create mode 100644 internal/otel_collector/internal/buildscripts/gen-certs.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/Dockerfile create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/common.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/deb/README.md create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/deb/build.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/otel-collector.service create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/postinstall.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/preinstall.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/preuninstall.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/README.md create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/build.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/fpm/test.sh create mode 100644 internal/otel_collector/internal/buildscripts/packaging/msi/make.ps1 create mode 100644 internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry-collector.wxs create mode 100644 internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry.ico create mode 100644 internal/otel_collector/internal/collector/telemetry/telemetry.go create mode 100644 internal/otel_collector/internal/data/.gitignore create mode 100644 internal/otel_collector/internal/data/bytesid.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.gw.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.gw.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_config.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.gw.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service_gateway_aliases.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/common/v1/common.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/logs/v1/logs.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/metrics/v1/metrics.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/resource/v1/resource.pb.go create mode 100644 internal/otel_collector/internal/data/opentelemetry-proto-gen/trace/v1/trace.pb.go create mode 100644 internal/otel_collector/internal/data/spanid.go create mode 100644 internal/otel_collector/internal/data/spanid_test.go create mode 100644 internal/otel_collector/internal/data/traceid.go create mode 100644 internal/otel_collector/internal/data/traceid_test.go create mode 100644 internal/otel_collector/internal/goldendataset/generator_commons.go create mode 100644 internal/otel_collector/internal/goldendataset/metric_gen.go create mode 100644 internal/otel_collector/internal/goldendataset/metric_gen_test.go create mode 100644 internal/otel_collector/internal/goldendataset/pict_metric_gen.go create mode 100644 internal/otel_collector/internal/goldendataset/pict_metric_gen_test.go create mode 100644 internal/otel_collector/internal/goldendataset/pict_metrics_input_defs.go create mode 100644 internal/otel_collector/internal/goldendataset/pict_tracing_input_defs.go create mode 100644 internal/otel_collector/internal/goldendataset/resource_generator.go create mode 100644 internal/otel_collector/internal/goldendataset/resource_generator_test.go create mode 100644 internal/otel_collector/internal/goldendataset/span_generator.go create mode 100644 internal/otel_collector/internal/goldendataset/span_generator_test.go create mode 100644 internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_metrics.txt create mode 100644 internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_spans.txt create mode 100644 internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_traces.txt create mode 100644 internal/otel_collector/internal/goldendataset/testdata/pict_input_metrics.txt create mode 100644 internal/otel_collector/internal/goldendataset/testdata/pict_input_spans.txt create mode 100644 internal/otel_collector/internal/goldendataset/testdata/pict_input_traces.txt create mode 100644 internal/otel_collector/internal/goldendataset/traces_generator.go create mode 100644 internal/otel_collector/internal/goldendataset/traces_generator_test.go create mode 100644 internal/otel_collector/internal/middleware/compression.go create mode 100644 internal/otel_collector/internal/middleware/compression_test.go create mode 100644 internal/otel_collector/internal/otlp_wrapper.go create mode 100644 internal/otel_collector/internal/processor/filterconfig/config.go create mode 100644 internal/otel_collector/internal/processor/filterconfig/config_test.go create mode 100644 internal/otel_collector/internal/processor/filterexpr/matcher.go create mode 100644 internal/otel_collector/internal/processor/filterexpr/matcher_test.go create mode 100644 internal/otel_collector/internal/processor/filterhelper/filterhelper.go create mode 100644 internal/otel_collector/internal/processor/filterhelper/filterhelper_test.go create mode 100644 internal/otel_collector/internal/processor/filterlog/filterlog.go create mode 100644 internal/otel_collector/internal/processor/filterlog/filterlog_test.go create mode 100644 internal/otel_collector/internal/processor/filtermatcher/attributematcher.go create mode 100644 internal/otel_collector/internal/processor/filtermatcher/filtermatcher.go create mode 100644 internal/otel_collector/internal/processor/filtermatcher/filtermatcher_test.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/config.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/config_test.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/doc.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/expr_matcher.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/filtermetric.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/filtermetric_test.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/helpers_test.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/name_matcher.go create mode 100644 internal/otel_collector/internal/processor/filtermetric/testdata/config.yaml create mode 100644 internal/otel_collector/internal/processor/filterset/config.go create mode 100644 internal/otel_collector/internal/processor/filterset/config_test.go create mode 100644 internal/otel_collector/internal/processor/filterset/doc.go create mode 100644 internal/otel_collector/internal/processor/filterset/filterset.go create mode 100644 internal/otel_collector/internal/processor/filterset/regexp/config.go create mode 100644 internal/otel_collector/internal/processor/filterset/regexp/config_test.go create mode 100644 internal/otel_collector/internal/processor/filterset/regexp/doc.go create mode 100644 internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset.go create mode 100644 internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset_test.go create mode 100644 internal/otel_collector/internal/processor/filterset/regexp/testdata/config.yaml create mode 100644 internal/otel_collector/internal/processor/filterset/strict/doc.go create mode 100644 internal/otel_collector/internal/processor/filterset/strict/strictfilterset.go create mode 100644 internal/otel_collector/internal/processor/filterset/strict/strictfilterset_test.go create mode 100644 internal/otel_collector/internal/processor/filterset/testdata/config.yaml create mode 100644 internal/otel_collector/internal/processor/filterset/testdata/config_invalid.yaml create mode 100644 internal/otel_collector/internal/processor/filterspan/filterspan.go create mode 100644 internal/otel_collector/internal/processor/filterspan/filterspan_test.go create mode 100644 internal/otel_collector/internal/testdata/common.go create mode 100644 internal/otel_collector/internal/testdata/log.go create mode 100644 internal/otel_collector/internal/testdata/log_test.go create mode 100644 internal/otel_collector/internal/testdata/metric.go create mode 100644 internal/otel_collector/internal/testdata/metric_test.go create mode 100644 internal/otel_collector/internal/testdata/resource.go create mode 100644 internal/otel_collector/internal/testdata/trace.go create mode 100644 internal/otel_collector/internal/testdata/trace_test.go create mode 100644 internal/otel_collector/internal/version/version.go create mode 100644 internal/otel_collector/internal/version/version_test.go create mode 100644 internal/otel_collector/obsreport/doc.go create mode 100644 internal/otel_collector/obsreport/observability.go create mode 100644 internal/otel_collector/obsreport/obsreport.go create mode 100644 internal/otel_collector/obsreport/obsreport_exporter.go create mode 100644 internal/otel_collector/obsreport/obsreport_processor.go create mode 100644 internal/otel_collector/obsreport/obsreport_receiver.go create mode 100644 internal/otel_collector/obsreport/obsreport_scraper.go create mode 100644 internal/otel_collector/obsreport/obsreport_test.go create mode 100644 internal/otel_collector/obsreport/obsreporttest/obsreporttest.go create mode 100644 internal/otel_collector/obsreport/obsreporttest/obsreporttest_test.go create mode 100644 internal/otel_collector/processor/README.md create mode 100644 internal/otel_collector/processor/attributesprocessor/README.md create mode 100644 internal/otel_collector/processor/attributesprocessor/attributes_log.go create mode 100644 internal/otel_collector/processor/attributesprocessor/attributes_log_test.go create mode 100644 internal/otel_collector/processor/attributesprocessor/attributes_trace.go create mode 100644 internal/otel_collector/processor/attributesprocessor/attributes_trace_test.go create mode 100644 internal/otel_collector/processor/attributesprocessor/config.go create mode 100644 internal/otel_collector/processor/attributesprocessor/config_test.go create mode 100644 internal/otel_collector/processor/attributesprocessor/doc.go create mode 100644 internal/otel_collector/processor/attributesprocessor/factory.go create mode 100644 internal/otel_collector/processor/attributesprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/attributesprocessor/testdata/config.yaml create mode 100644 internal/otel_collector/processor/batchprocessor/README.md create mode 100644 internal/otel_collector/processor/batchprocessor/batch_processor.go create mode 100644 internal/otel_collector/processor/batchprocessor/batch_processor_test.go create mode 100644 internal/otel_collector/processor/batchprocessor/config.go create mode 100644 internal/otel_collector/processor/batchprocessor/config_test.go create mode 100644 internal/otel_collector/processor/batchprocessor/factory.go create mode 100644 internal/otel_collector/processor/batchprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/batchprocessor/metrics.go create mode 100644 internal/otel_collector/processor/batchprocessor/metrics_test.go create mode 100644 internal/otel_collector/processor/batchprocessor/splittraces.go create mode 100644 internal/otel_collector/processor/batchprocessor/splittraces_test.go create mode 100644 internal/otel_collector/processor/batchprocessor/testdata/config.yaml create mode 100644 internal/otel_collector/processor/cloningfanoutconnector.go create mode 100644 internal/otel_collector/processor/cloningfanoutconnector_test.go create mode 100644 internal/otel_collector/processor/fanoutconnector.go create mode 100644 internal/otel_collector/processor/fanoutconnector_test.go create mode 100644 internal/otel_collector/processor/filterprocessor/README.md create mode 100644 internal/otel_collector/processor/filterprocessor/config.go create mode 100644 internal/otel_collector/processor/filterprocessor/config_test.go create mode 100644 internal/otel_collector/processor/filterprocessor/doc.go create mode 100644 internal/otel_collector/processor/filterprocessor/expr_test.go create mode 100644 internal/otel_collector/processor/filterprocessor/factory.go create mode 100644 internal/otel_collector/processor/filterprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/filterprocessor/filter_processor.go create mode 100644 internal/otel_collector/processor/filterprocessor/filter_processor_test.go create mode 100644 internal/otel_collector/processor/filterprocessor/metric_index.go create mode 100644 internal/otel_collector/processor/filterprocessor/testdata/config_expr.yaml create mode 100644 internal/otel_collector/processor/filterprocessor/testdata/config_invalid.yaml create mode 100644 internal/otel_collector/processor/filterprocessor/testdata/config_regexp.yaml create mode 100644 internal/otel_collector/processor/filterprocessor/testdata/config_strict.yaml create mode 100644 internal/otel_collector/processor/memorylimiter/README.md create mode 100644 internal/otel_collector/processor/memorylimiter/config.go create mode 100644 internal/otel_collector/processor/memorylimiter/config_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/factory.go create mode 100644 internal/otel_collector/processor/memorylimiter/factory_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/doc.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/errors.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_period_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_quota_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/empty/cpu.cfs_quota_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/invalid/cpu.cfs_quota_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined-period/cpu.cfs_quota_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_period_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_quota_us create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/cgroup create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/mountinfo create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-cgroup/cgroup create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-mountinfo/mountinfo create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/cgroup create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/mountinfo create mode 100644 internal/otel_collector/processor/memorylimiter/internal/cgroups/util_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other.go create mode 100644 internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/memorylimiter.go create mode 100644 internal/otel_collector/processor/memorylimiter/memorylimiter_test.go create mode 100644 internal/otel_collector/processor/memorylimiter/testdata/config.yaml create mode 100644 internal/otel_collector/processor/metrics.go create mode 100644 internal/otel_collector/processor/metrics_test.go create mode 100644 internal/otel_collector/processor/processorhelper/attraction.go create mode 100644 internal/otel_collector/processor/processorhelper/attraction_test.go create mode 100644 internal/otel_collector/processor/processorhelper/factory.go create mode 100644 internal/otel_collector/processor/processorhelper/factory_test.go create mode 100644 internal/otel_collector/processor/processorhelper/hasher.go create mode 100644 internal/otel_collector/processor/processorhelper/processor.go create mode 100644 internal/otel_collector/processor/processorhelper/processor_test.go create mode 100644 internal/otel_collector/processor/queuedprocessor/README.md create mode 100644 internal/otel_collector/processor/queuedprocessor/config.go create mode 100644 internal/otel_collector/processor/queuedprocessor/config_test.go create mode 100644 internal/otel_collector/processor/queuedprocessor/factory.go create mode 100644 internal/otel_collector/processor/queuedprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/queuedprocessor/metrics.go create mode 100644 internal/otel_collector/processor/queuedprocessor/queued_processor.go create mode 100644 internal/otel_collector/processor/queuedprocessor/queued_processor_test.go create mode 100644 internal/otel_collector/processor/queuedprocessor/testdata/config.yaml create mode 100644 internal/otel_collector/processor/resourceprocessor/README.md create mode 100644 internal/otel_collector/processor/resourceprocessor/config.go create mode 100644 internal/otel_collector/processor/resourceprocessor/config_test.go create mode 100644 internal/otel_collector/processor/resourceprocessor/doc.go create mode 100644 internal/otel_collector/processor/resourceprocessor/factory.go create mode 100644 internal/otel_collector/processor/resourceprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/resourceprocessor/resource_processor.go create mode 100644 internal/otel_collector/processor/resourceprocessor/resource_processor_test.go create mode 100644 internal/otel_collector/processor/resourceprocessor/testdata/config.yaml create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/README.md create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config.go create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config_test.go create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory.go create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler.go create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler_test.go create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/config.yaml create mode 100644 internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/empty.yaml create mode 100644 internal/otel_collector/processor/spanprocessor/README.md create mode 100644 internal/otel_collector/processor/spanprocessor/config.go create mode 100644 internal/otel_collector/processor/spanprocessor/config_test.go create mode 100644 internal/otel_collector/processor/spanprocessor/doc.go create mode 100644 internal/otel_collector/processor/spanprocessor/factory.go create mode 100644 internal/otel_collector/processor/spanprocessor/factory_test.go create mode 100644 internal/otel_collector/processor/spanprocessor/span.go create mode 100644 internal/otel_collector/processor/spanprocessor/span_test.go create mode 100644 internal/otel_collector/processor/spanprocessor/testdata/config.yaml create mode 100644 internal/otel_collector/proto_patch.sed create mode 100644 internal/otel_collector/receiver/README.md create mode 100644 internal/otel_collector/receiver/doc.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/README.md create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/ack.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/ack_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/collector.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/config.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/conversion.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/conversion_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/factory.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/heartbeat.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/heartbeat_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/parse_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/receiver.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/receiver_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/server.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/server_test.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-event.hexdump create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-packed-compressed.hexdump create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-packed.hexdump create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/testdata/message-event.hexdump create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/timeext.go create mode 100644 internal/otel_collector/receiver/fluentforwardreceiver/timeext_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/README.md create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/codegen.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/example_config.yaml create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/metadata/generated_metrics.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/doc.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_mock.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_linux.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_fallback.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_linux.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_unix.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_linux.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_unix.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_linux.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/config.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/pagefile_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_metadata.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows_test.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/testutils.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/internal/utils.go create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/metadata.yaml create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-invalidscraperkey.yaml create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-noscrapers.yaml create mode 100644 internal/otel_collector/receiver/hostmetricsreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/jaegerreceiver/README.md create mode 100644 internal/otel_collector/receiver/jaegerreceiver/config.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/errors.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/factory.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/jaeger_agent_test.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/bad_empty_config.yaml create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/bad_no_proto_config.yaml create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/bad_proto_config.yaml create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/bad_typo_default_proto_config.yaml create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/ca.crt create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/client.crt create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/client.key create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/server.crt create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/server.key create mode 100644 internal/otel_collector/receiver/jaegerreceiver/testdata/strategies.json create mode 100644 internal/otel_collector/receiver/jaegerreceiver/trace_receiver.go create mode 100644 internal/otel_collector/receiver/jaegerreceiver/trace_receiver_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/README.md create mode 100644 internal/otel_collector/receiver/kafkareceiver/config.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/config_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/factory.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/kafka_receiver.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/kafka_receiver_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/metrics.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/metrics_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/kafkareceiver/unmarshaller.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/unmarshaller_test.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller.go create mode 100644 internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/README.md create mode 100644 internal/otel_collector/receiver/opencensusreceiver/config.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/factory.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/ocmetrics/doc.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/octrace/doc.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/octrace/observability_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/octrace/options.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/opencensus.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/opencensus_test.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/options.go create mode 100644 internal/otel_collector/receiver/opencensusreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/otlpreceiver/README.md create mode 100644 internal/otel_collector/receiver/otlpreceiver/config.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/factory.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/logs/otlp.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/logs/otlp_test.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb_test.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/metrics/otlp.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/metrics/otlp_test.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/mixin.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/otlp.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/otlp_test.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/otlphttp.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/testdata/bad_empty_config.yaml create mode 100644 internal/otel_collector/receiver/otlpreceiver/testdata/bad_no_proto_config.yaml create mode 100644 internal/otel_collector/receiver/otlpreceiver/testdata/bad_proto_config.yaml create mode 100644 internal/otel_collector/receiver/otlpreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/otlpreceiver/testdata/typo_default_proto_config.yaml create mode 100644 internal/otel_collector/receiver/otlpreceiver/trace/otlp.go create mode 100644 internal/otel_collector/receiver/otlpreceiver/trace/otlp_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/DESIGN.md create mode 100644 internal/otel_collector/receiver/prometheusreceiver/README.md create mode 100644 internal/otel_collector/receiver/prometheusreceiver/config.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/doc.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/factory.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/internal_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/logger.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/logger_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/metadata.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/metricfamily.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/ocastore.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/ocastore_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/transaction.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/internal/transaction_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/metrics_receiver.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/metrics_receiver_test.go create mode 100644 internal/otel_collector/receiver/prometheusreceiver/scrapeloop-flowchart.png create mode 100644 internal/otel_collector/receiver/prometheusreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/prometheusreceiver/testdata/config_env.yaml create mode 100644 internal/otel_collector/receiver/prometheusreceiver/testdata/config_k8s.yaml create mode 100644 internal/otel_collector/receiver/prometheusreceiver/testdata/config_sd.yaml create mode 100644 internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-prometheus-section.yaml create mode 100644 internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-section.yaml create mode 100644 internal/otel_collector/receiver/receiverhelper/factory.go create mode 100644 internal/otel_collector/receiver/receiverhelper/factory_test.go create mode 100644 internal/otel_collector/receiver/scraperhelper/errors.go create mode 100644 internal/otel_collector/receiver/scraperhelper/errors_test.go create mode 100644 internal/otel_collector/receiver/scraperhelper/scraper.go create mode 100644 internal/otel_collector/receiver/scraperhelper/scrapercontroller.go create mode 100644 internal/otel_collector/receiver/scraperhelper/scrapercontroller_test.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/README.md create mode 100644 internal/otel_collector/receiver/zipkinreceiver/config.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/config_test.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/factory.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/factory_test.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/proto_parse_test.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/testdata/config.yaml create mode 100644 internal/otel_collector/receiver/zipkinreceiver/testdata/sample1.json create mode 100644 internal/otel_collector/receiver/zipkinreceiver/testdata/sample2.json create mode 100644 internal/otel_collector/receiver/zipkinreceiver/trace_receiver.go create mode 100644 internal/otel_collector/receiver/zipkinreceiver/trace_receiver_test.go create mode 100644 internal/otel_collector/service/builder/builder.go create mode 100644 internal/otel_collector/service/builder/doc.go create mode 100644 internal/otel_collector/service/builder/exporters_builder.go create mode 100644 internal/otel_collector/service/builder/exporters_builder_test.go create mode 100644 internal/otel_collector/service/builder/extensions_builder.go create mode 100644 internal/otel_collector/service/builder/pipelines_builder.go create mode 100644 internal/otel_collector/service/builder/pipelines_builder_test.go create mode 100644 internal/otel_collector/service/builder/receivers_builder.go create mode 100644 internal/otel_collector/service/builder/receivers_builder_test.go create mode 100644 internal/otel_collector/service/builder/testdata/bad_processor_factory.yaml create mode 100644 internal/otel_collector/service/builder/testdata/bad_receiver_factory.yaml create mode 100644 internal/otel_collector/service/builder/testdata/pipelines_builder.yaml create mode 100644 internal/otel_collector/service/builder/testdata/unused_receiver.yaml create mode 100644 internal/otel_collector/service/defaultcomponents/defaults.go create mode 100644 internal/otel_collector/service/defaultcomponents/defaults_test.go create mode 100644 internal/otel_collector/service/defaultcomponents/docs_test.go create mode 100644 internal/otel_collector/service/internal/gen.go create mode 100644 internal/otel_collector/service/internal/resources.go create mode 100644 internal/otel_collector/service/internal/telemetry/process_telemetry.go create mode 100644 internal/otel_collector/service/internal/telemetry/process_telemetry_test.go create mode 100644 internal/otel_collector/service/internal/templates.go create mode 100644 internal/otel_collector/service/internal/templates/component_header.html create mode 100644 internal/otel_collector/service/internal/templates/extensions_table.html create mode 100644 internal/otel_collector/service/internal/templates/footer.html create mode 100644 internal/otel_collector/service/internal/templates/header.html create mode 100644 internal/otel_collector/service/internal/templates/pipelines_table.html create mode 100644 internal/otel_collector/service/internal/templates/properties_table.html create mode 100644 internal/otel_collector/service/internal/templates_test.go create mode 100644 internal/otel_collector/service/logger.go create mode 100644 internal/otel_collector/service/service.go create mode 100644 internal/otel_collector/service/service_test.go create mode 100644 internal/otel_collector/service/service_windows.go create mode 100644 internal/otel_collector/service/service_windows_test.go create mode 100644 internal/otel_collector/service/set_flag.go create mode 100644 internal/otel_collector/service/set_flag_test.go create mode 100644 internal/otel_collector/service/telemetry.go create mode 100644 internal/otel_collector/service/testdata/otelcol-config-minimal.yaml create mode 100644 internal/otel_collector/service/testdata/otelcol-config.yaml create mode 100644 internal/otel_collector/testbed/CCRepo_result.png create mode 100644 internal/otel_collector/testbed/README.md create mode 100644 internal/otel_collector/testbed/correctness/.gitignore create mode 100644 internal/otel_collector/testbed/correctness/metrics/correctness_test_case.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/doc.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/metric_diff.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/metric_diff_test.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/metric_index.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/metric_supplier.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/metrics_correctness_test.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/metrics_test_harness.go create mode 100644 internal/otel_collector/testbed/correctness/metrics/results_dir.go create mode 100644 internal/otel_collector/testbed/correctness/testdata/generated_pict_pairs_metrics_pipeline.txt create mode 100644 internal/otel_collector/testbed/correctness/testdata/pict_input_metrics_pipeline.txt create mode 100644 internal/otel_collector/testbed/correctness/traces/.gitignore create mode 100644 internal/otel_collector/testbed/correctness/traces/correctness_test.go create mode 100644 internal/otel_collector/testbed/correctness/traces/runtests.sh create mode 100644 internal/otel_collector/testbed/correctness/traces/testdata/generated_pict_pairs_traces_pipeline.txt create mode 100644 internal/otel_collector/testbed/correctness/traces/testdata/pict_input_traces_pipeline.txt create mode 100644 internal/otel_collector/testbed/correctness/utils.go create mode 100644 internal/otel_collector/testbed/correctness_result.png create mode 100644 internal/otel_collector/testbed/e2e_diagram.jpeg create mode 100644 internal/otel_collector/testbed/testbed/.gitignore create mode 100644 internal/otel_collector/testbed/testbed/child_process.go create mode 100644 internal/otel_collector/testbed/testbed/data_providers.go create mode 100644 internal/otel_collector/testbed/testbed/data_providers_test.go create mode 100644 internal/otel_collector/testbed/testbed/load_generator.go create mode 100644 internal/otel_collector/testbed/testbed/mock_backend.go create mode 100644 internal/otel_collector/testbed/testbed/mock_backend_test.go create mode 100644 internal/otel_collector/testbed/testbed/mockconsumer.go create mode 100644 internal/otel_collector/testbed/testbed/options.go create mode 100644 internal/otel_collector/testbed/testbed/otelcol_runner.go create mode 100644 internal/otel_collector/testbed/testbed/otelcol_runner_test.go create mode 100644 internal/otel_collector/testbed/testbed/receivers.go create mode 100644 internal/otel_collector/testbed/testbed/results.go create mode 100644 internal/otel_collector/testbed/testbed/senders.go create mode 100644 internal/otel_collector/testbed/testbed/test_bed.go create mode 100644 internal/otel_collector/testbed/testbed/test_case.go create mode 100644 internal/otel_collector/testbed/testbed/utils.go create mode 100644 internal/otel_collector/testbed/testbed/validator.go create mode 100644 internal/otel_collector/testbed/tests/.gitignore create mode 100644 internal/otel_collector/testbed/tests/e2e_test.go create mode 100644 internal/otel_collector/testbed/tests/log_test.go create mode 100644 internal/otel_collector/testbed/tests/metric_test.go create mode 100644 internal/otel_collector/testbed/tests/resource_processor_test.go create mode 100644 internal/otel_collector/testbed/tests/results/BASELINE.md create mode 100644 internal/otel_collector/testbed/tests/runtests.sh create mode 100644 internal/otel_collector/testbed/tests/scenarios.go create mode 100644 internal/otel_collector/testbed/tests/testdata/agent-config.yaml create mode 100644 internal/otel_collector/testbed/tests/testdata/memory-limiter.yaml create mode 100644 internal/otel_collector/testbed/tests/trace_test.go create mode 100644 internal/otel_collector/testbed/tests/utils.go create mode 100644 internal/otel_collector/testutil/logstest/logs.go create mode 100644 internal/otel_collector/testutil/logstest/logs_test.go create mode 100644 internal/otel_collector/testutil/metricstestutil/metricsutil.go create mode 100644 internal/otel_collector/testutil/metricstestutil/metricsutil_test.go create mode 100644 internal/otel_collector/testutil/testutil.go create mode 100644 internal/otel_collector/testutil/testutil_test.go create mode 100644 internal/otel_collector/translator/conventions/opencensus.go create mode 100644 internal/otel_collector/translator/conventions/opentelemetry.go create mode 100644 internal/otel_collector/translator/internaldata/metrics_to_oc.go create mode 100644 internal/otel_collector/translator/internaldata/metrics_to_oc_test.go create mode 100644 internal/otel_collector/translator/internaldata/oc_testdata_test.go create mode 100644 internal/otel_collector/translator/internaldata/oc_to_metrics.go create mode 100644 internal/otel_collector/translator/internaldata/oc_to_metrics_test.go create mode 100644 internal/otel_collector/translator/internaldata/oc_to_resource.go create mode 100644 internal/otel_collector/translator/internaldata/oc_to_resource_test.go create mode 100644 internal/otel_collector/translator/internaldata/oc_to_traces.go create mode 100644 internal/otel_collector/translator/internaldata/oc_to_traces_test.go create mode 100644 internal/otel_collector/translator/internaldata/resource_to_oc.go create mode 100644 internal/otel_collector/translator/internaldata/resource_to_oc_test.go create mode 100644 internal/otel_collector/translator/internaldata/traces_to_oc.go create mode 100644 internal/otel_collector/translator/internaldata/traces_to_oc_test.go create mode 100644 internal/otel_collector/translator/trace/README.md create mode 100644 internal/otel_collector/translator/trace/big_endian_converter.go create mode 100644 internal/otel_collector/translator/trace/big_endian_converter_test.go create mode 100644 internal/otel_collector/translator/trace/grpc_http_mapper.go create mode 100644 internal/otel_collector/translator/trace/grpc_http_mapper_test.go create mode 100644 internal/otel_collector/translator/trace/jaeger/constants.go create mode 100644 internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces.go create mode 100644 internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces_test.go create mode 100644 internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces.go create mode 100644 internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces_test.go create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_01.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_02.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_01.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_02.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_01.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_02.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_01.json create mode 100644 internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_02.json create mode 100644 internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto.go create mode 100644 internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto_test.go create mode 100644 internal/otel_collector/translator/trace/opencensus_helper.go create mode 100644 internal/otel_collector/translator/trace/protospan_translation.go create mode 100644 internal/otel_collector/translator/trace/protospan_translation_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/attributekeys.go create mode 100644 internal/otel_collector/translator/trace/zipkin/status_code.go create mode 100644 internal/otel_collector/translator/trace/zipkin/status_code_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_error_batch.json create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_local_component.json create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_multiple_batches.json create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_single_batch.json create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_local_component.json create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_single_batch.json create mode 100644 internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v2_single.json create mode 100644 internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2.go create mode 100644 internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces_test.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces.go create mode 100644 internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces_test.go diff --git a/NOTICE.txt b/NOTICE.txt index 73595e6e03a..7b8c7cc2b87 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -4281,7 +4281,7 @@ Version: v0.17.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector@v0.17.0/LICENSE: +Contents of probable licence file LICENSE: Apache License diff --git a/go.mod b/go.mod index 34212d0cd9e..22902e18ae2 100644 --- a/go.mod +++ b/go.mod @@ -89,3 +89,5 @@ replace ( k8s.io/apimachinery => k8s.io/apimachinery v0.19.4 k8s.io/client-go => k8s.io/client-go v0.19.4 ) + +replace go.opentelemetry.io/collector => ./internal/otel_collector diff --git a/go.sum b/go.sum index 9ac3767ff88..b16b5e80008 100644 --- a/go.sum +++ b/go.sum @@ -1234,6 +1234,8 @@ go.elastic.co/fastjson v1.1.0 h1:3MrGBWWVIxe/xvsbpghtkFoPciPhOCmjsR/HfwEeQR4= go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI= go.elastic.co/go-licence-detector v0.4.0 h1:it5dP+6LPxLsosdhtbAqk/zJQxzS0QSSpdNkKVuwKMs= go.elastic.co/go-licence-detector v0.4.0/go.mod h1:fSJQU8au4SAgDK+UQFbgUPsXKYNBDv4E/dwWevrMpXU= +go.elastic.co/go-licence-detector v0.5.0 h1:YXPCyt9faKMdJ8uMrkcI4patk8WZ0ME5oaIhYBUsRU4= +go.elastic.co/go-licence-detector v0.5.0/go.mod h1:fSJQU8au4SAgDK+UQFbgUPsXKYNBDv4E/dwWevrMpXU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= diff --git a/internal/otel_collector/CHANGELOG.md b/internal/otel_collector/CHANGELOG.md new file mode 100644 index 00000000000..c91fbc7f6ba --- /dev/null +++ b/internal/otel_collector/CHANGELOG.md @@ -0,0 +1,653 @@ +# Changelog + +## Unreleased + +## v0.17.0 Beta + +## 💡 Enhancements 💡 + +- Default config environment variable expansion (#2231) +- `prometheusremotewrite` exporter: Add batched exports (#2249) +- `memorylimiter` processor: Introduce soft and hard limits (#2250) + +## 🧰 Bug fixes 🧰 + +- Fix nits in pdata usage (#2235) +- Convert status to not be a pointer in the Span (#2242) +- Report the error from `pprof.StartCPUProfile` (#2263) +- Rename `service.Application.SignalTestComplete` to `Shutdown` (#2277) + +## v0.16.0 Beta + +## 🛑 Breaking changes 🛑 + +- Rename Push functions to be consistent across signals in `exporterhelper` (#2203) + +## 💡 Enhancements 💡 + +- Change default OTLP/gRPC port number to 4317, also continue receiving on legacy port + 55680 during transition period (#2104). +- `kafka` exporter: Add support for exporting metrics as otlp Protobuf. (#1966) +- Move scraper helpers to its own `scraperhelper` package (#2185) +- Add `componenthelper` package to help build components (#2186) +- Remove usage of custom init/stop in `scraper` and use start/shutdown from `component` (#2193) +- Add more trace annotations, so zpages are more useful to determine failures (#2206) +- Add support to skip TLS verification (#2202) +- Expose non-nullable metric types (#2208) +- Expose non-nullable elements from slices of pointers (#2200) + +## 🧰 Bug fixes 🧰 + +- Change InstrumentationLibrary to be non-nullable (#2196) +- Add support for slices to non-pointers, use non-nullable AnyValue (#2192) +- Fix `--set` flag to work with `{}` in configs (#2162) + +## v0.15.0 Beta + +## 🛑 Breaking changes 🛑 + +- Remove legacy metrics, they were marked as legacy for ~12 months #2105 + +## 💡 Enhancements 💡 + +- Implement conversion between OpenCensus and OpenTelemetry Summary Metric (#2048) +- Add ability to generate non nullable messages (#2005) +- Implement Summary Metric in Prometheus RemoteWrite Exporter (#2083) +- Add `resource_to_telemetry_conversion` to exporter helper expose exporter settings (#2060) +- Add `CustomRoundTripper` function to httpclientconfig (#2085) +- Allow for more logging options to be passed to `service` (#2132) +- Add config parameters for `jaeger` receiver (#2068) +- Map unset status code for `jaegar` translator as per spec (#2134) +- Add more trace annotations to the queue-retry logic (#2136) +- Add config settings for component telemetry (#2148) +- Use net.SplitHostPort for IPv6 support in `prometheus` receiver (#2154) +- Add --log-format command line option (default to "console") #2177. + +## 🧰 Bug fixes 🧰 + +- `logging` exporter: Add Logging for Summary Datapoint (#2084) +- `hostmetrics` receiver: use correct TCP state labels on Unix systems (#2087) +- Fix otlp_log receiver wrong use of trace measurement (#2117) +- Fix "process/memory/rss" metric units (#2112) +- Fix "process/cpu_seconds" metrics (#2113) +- Add check for nil logger in exporterhelper functions (#2141) +- `prometheus` receiver: + - Upgrade Prometheus version to fix race condition (#2121) + - Fix the scraper/discover manager coordination (#2089) + - Fix panic when adjusting buckets (#2168) + +## v0.14.0 Beta + +## 🚀 New components 🚀 + +- `otlphttp` exporter which implements OTLP over HTTP protocol. + +## 🛑 Breaking changes 🛑 + +- Rename consumer.TraceConsumer to consumer.TracesConsumer #1974 +- Rename component.TraceReceiver to component.TracesReceiver #1975 +- Rename component.TraceProcessor to component.TracesProcessor #1976 +- Rename component.TraceExporter to component.TracesExporter #1975 +- Deprecate NopExporter, add NopConsumer (#1972) +- Deprecate SinkExporter, add SinkConsumer (#1973) +- Move `tailsampling` processor to contrib (#2012) +- Remove NewAttributeValueSlice (#2028) and mark NewAttributeValue as deprecated (#2022) +- Remove pdata.StringValue (#2021) +- Remove pdata.InitFromAttributeMap, use CopyTo if needed (#2042) +- Remove SetMapVal and SetArrayVal for pdata.AttributeValue (#2039) + +## 💡 Enhancements 💡 + +- `zipkin` exporter: Add queue retry to zipkin (#1971) +- `prometheus` exporter: Add `send_timestamps` option (#1951) +- `filter` processor: Add `expr` pdata.Metric filtering support (#1940, #1996) +- `attribute` processor: Add log support (#1934) +- `logging` exporter: Add index for histogram buckets count (#2009) +- `otlphttp` exporter: Add correct handling of server error responses (#2016) +- `prometheusremotewrite` exporter: + - Add user agent header to outgoing http request (#2000) + - Convert histograms to cumulative (#2049) + - Return permanent errors (#2053) + - Add external labels (#2044) +- `hostmetrics` receiver: Use scraper controller (#1949) +- Change Span/Trace ID to be byte array (#2001) +- Add `simple` metrics helper to facilitate building pdata.Metrics in receivers (#1540) +- Improve diagnostic logging for exporters (#2020) +- Add obsreport to receiverhelper scrapers (#1961) +- Update OTLP to 0.6.0 and use the new Span Status code (#2031) +- Add support of partial requests for logs and metrics to the exporterhelper (#2059) + +## 🧰 Bug fixes 🧰 + +- `logging` exporter: Added array serialization (#1994) +- `zipkin` receiver: Allow receiver to parse string tags (#1893) +- `batch` processor: Fix shutdown race (#1967) +- Guard for nil data points (#2055) + +## v0.13.0 Beta + +## 🛑 Breaking changes 🛑 + +- Host metric `system.disk.time` renamed to `system.disk.operation_time` (#1887) +- Use consumer for sender interface, remove unnecessary receiver address from Runner (#1941) +- Enable sending queue by default in all exporters configured to use it (#1924) +- Removed `groupbytraceprocessor` (#1891) +- Remove ability to configure collection interval per scraper (#1947) + +## 💡 Enhancements 💡 + +- Host Metrics receiver now reports both `system.disk.io_time` and `system.disk.operation_time` (#1887) +- Match spans against the instrumentation library and resource attributes (#928) +- Add `receiverhelper` for creating flexible "scraper" metrics receiver (#1886, #1890, #1945, #1946) +- Migrate `tailsampling` processor to new OTLP-based internal data model and add Composite Sampler (#1894) +- Metadata Generator: Change Metrics fields to implement an interface with new methods (#1912) +- Add unmarshalling for `pdata.Traces` (#1948) +- Add debug-level message on error for `jaeger` exporter (#1964) + +## 🧰 Bug fixes 🧰 + +- Fix bug where the service does not correctly start/stop the log exporters (#1943) +- Fix Queued Retry Unusable without Batch Processor (#1813) - (#1930) +- `prometheus` receiver: Log error message when `process_start_time_seconds` gauge is missing (#1921) +- Fix trace jaeger conversion to internal traces zero time bug (#1957) +- Fix panic in otlp traces to zipkin (#1963) +- Fix OTLP/HTTP receiver's path to be /v1/traces (#1979) + +## v0.12.0 Beta + +## 🚀 New components 🚀 + +- `configauth` package with the auth settings that can be used by receivers (#1807, #1808, #1809, #1810) +- `perfcounters` package that uses perflib for host metrics receiver (#1835, #1836, #1868, #1869, #1870) + +## 💡 Enhancements 💡 + +- Remove `queued_retry` and enable `otlp` metrics receiver in default config (#1823, #1838) +- Add `limit_percentage` and `spike_limit_percentage` options to `memorylimiter` processor (#1622) +- `hostmetrics` receiver: + - Collect additional labels from partitions in the filesystems scraper (#1858) + - Add filters for mount point and filesystem type (#1866) +- Add cloud.provider semantic conventions (#1865) +- `attribute` processor: Add log support (#1783) +- Deprecate OpenCensus-based internal data structures (#1843) +- Introduce SpanID data type, not yet used in Protobuf messages ($1854, #1855) +- Enable `otlp` trace by default in the released docker image (#1883) +- `tailsampling` processor: Combine batches of spans into a single batch (#1864) +- `filter` processor: Update to use pdata (#1885) +- Allow MSI upgrades (#1914) + +## 🧰 Bug fixes 🧰 + +- `prometheus` receiver: Print a more informative message about 'up' metric value (#1826) +- Use custom data type and custom JSON serialization for traceid (#1840) +- Skip creation of redundant nil resource in translation from OC if there are no combined metrics (#1803) +- `tailsampling` processor: Only send to next consumer once (#1735) +- Report Windows pagefile usage in bytes (#1837) +- Fix issue where Prometheus SD config cannot be parsed (#1877) + +## v0.11.0 Beta + +## 🛑 Breaking changes 🛑 + +- Rename service.Start() to Run() since it's a blocking call +- Fix slice Append to accept by value the element in pdata +- Change CreateTraceProcessor and CreateMetricsProcessor to use the same parameter order as receivers/logs processor and exporters. +- Prevent accidental use of LogsToOtlp and LogsFromOtlp and the OTLP data structs (#1703) +- Remove SetType from configmodels, ensure all registered factories set the type in config (#1798) +- Move process telemetry to service/internal (#1794) + +## 💡 Enhancements 💡 + +- Add map and array attribute value type support (#1656) +- Add authentication support to kafka (#1632) +- Implement InstrumentationLibrary translation to jaeger (#1645) +- Add public functions to export pdata to ExportXServicesRequest Protobuf bytes (#1741) +- Expose telemetry level in the configtelemetry (#1796) +- Add configauth package (#1807) +- Add config to docker image (#1792) + +## 🧰 Bug fixes 🧰 + +- Use zap int argument for int values instead of conversion (#1779) +- Add support for gzip encoded payload in OTLP/HTTP receiver (#1581) +- Return proto status for OTLP receiver when failed (#1788) + +## v0.10.0 Beta + +## 🛑 Breaking changes 🛑 + +- **Update OTLP to v0.5.0, incompatible metrics protocol.** +- Remove support for propagating summary metrics in OtelCollector. + - This is a temporary change, and will affect mostly OpenCensus users who use metrics. + +## 💡 Enhancements 💡 +- Support zipkin proto in `kafka` receiver (#1646) +- Prometheus Remote Write Exporter supporting Cortex (#1577, #1643) +- Add deployment environment semantic convention (#1722) +- Add logs support to `batch` and `resource` processors (#1723, #1729) + +## 🧰 Bug fixes 🧰 +- Identify config error when expected map is other value type (#1641) +- Fix Kafka receiver closing ready channel multiple times (#1696) +- Fix a panic issue while processing Zipkin spans with an empty service name (#1742) +- Zipkin Receiver: Always set the endtime (#1750) + +## v0.9.0 Beta + +## 🛑 Breaking changes 🛑 + +- **Remove old base factories**: + - `ReceiverFactoryBase` (#1583) + - `ProcessorFactoryBase` (#1596) + - `ExporterFactoryBase` (#1630) +- Remove logs factories and merge with normal factories (#1569) +- Remove `reconnection_delay` from OpenCensus exporter (#1516) +- Remove `ConsumerOld` interfaces (#1631) + +## 🚀 New components 🚀 +- `prometheusremotewrite` exporter: Send metrics data in Prometheus TimeSeries format to Cortex or any Prometheus (#1544) +- `kafka` receiver: Receive traces from Kafka (#1410) + +## 💡 Enhancements 💡 +- `kafka` exporter: Enable queueing, retry, timeout (#1455) +- Add `Headers` field in HTTPClientSettings (#1552) +- Change OpenCensus receiver (#1556) and exporter (#1571) to the new interfaces +- Add semantic attribute for `telemetry.auto.version` (#1578) +- Add uptime and RSS memory self-observability metrics (#1549) +- Support conversion for OpenCensus `SameProcessAsParentSpan` (#1629) +- Access application version in components (#1559) +- Make Kafka payload encoding configurable (#1584) + +## 🧰 Bug fixes 🧰 +- Stop further processing if `filterprocessor` filters all data (#1500) +- `processscraper`: Use same scrape time for all data points coming from same process (#1539) +- Ensure that time conversion for 0 returns nil timestamps or Time where IsZero returns true (#1550) +- Fix multiple exporters panic (#1563) +- Allow `attribute` processor for external use (#1574) +- Do not duplicate filesystem metrics for devices with many mount points (#1617) + +## v0.8.0 Beta + +## 🚀 New components 🚀 + +- `groupbytrace` processor that waits for a trace to be completed (#1362) + +## 💡 Enhancements 💡 + +- Migrate `zipkin` receiver/exporter to the new interfaces (#1484) +- Migrate `prometheus` receiver/exporter to the new interfaces (#1477, #1515) +- Add new FactoryUnmarshaler support to all components, deprecate old way (#1468) +- Update `fileexporter` to write data in OTLP (#1488) +- Add extension factory helper (#1485) +- Host scrapers: Use same scrape time for all data points coming from same source (#1473) +- Make logs SeverityNumber publicly available (#1496) +- Add recently included conventions for k8s and container resources (#1519) +- Add new config StartTimeMetricRegex to `prometheus` receiver (#1511) +- Convert Zipkin receiver and exporter to use OTLP (#1446) + +## 🧰 Bug fixes 🧰 + +- Infer OpenCensus resource type based on OpenTelemetry's semantic conventions (#1462) +- Fix log adapter in `prometheus` receiver (#1493) +- Avoid frequent errors for process telemetry on Windows (#1487) + +## v0.7.0 Beta + +## 🚀 New components 🚀 + +- Receivers + - `fluentfoward` runs a TCP server that accepts events via the [Fluent Forward protocol](https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1) (#1173) +- Exporters + - `kafka` exports traces to Kafka (#1439) +- Extensions + - **Experimental** `fluentbit` facilitates running a FluentBit subprocess of the collector (#1381) + +## 💡 Enhancements 💡 + +- Updated `golang/protobuf` from v1.3.5 to v1.4.2 (#1308) +- Updated `opencensus-proto` from v0.2.1 to v0.3.0 (#1308) +- Added round_robin `balancer_name` as an option to gRPC client settings (#1353) +- `hostmetrics` receiver + - Switch to using perf counters to get disk io metrics on Windows (#1340) + - Add device filter for file system (#1379) and disk (#1378) scrapers + - Record process physical & virtual memory stats separately (#1403) + - Scrape system.disk.time on Windows (#1408) + - Add disk.pending_operations metric (#1428) + - Add network interface label to network metrics (#1377) +- Add `exporterhelper` (#1351) and `processorhelper` (#1359) factories +- Update OTLP to latest version (#1384) +- Disable timeout, retry on failure and sending queue for `logging` exporter (#1400) +- Add support for retry and sending queue for `jaeger` exporter (#1401) +- Add batch size bytes metric to `batch` processor (#1270) +- `otlp` receiver: Add Log Support (#1444) +- Allow to configure read/write buffer sizes for http Client (#1447) +- Update DB conventions to latest and add exception conventions (#1452) + +## 🧰 Bug fixes 🧰 + +- Fix `resource` processor for old metrics (#1412) +- `jaeger` receiver: Do not try to stop if failed to start. Collector service will do that (#1434) + +## v0.6.0 Beta + +## 🛑 Breaking changes 🛑 + +- Renamed the metrics generated by `hostmetrics` receiver to match the (currently still pending) OpenTelemetry system metric conventions (#1261) (#1269) +- Removed `vmmetrics` receiver (#1282) +- Removed `cpu` scraper `report_per_cpu` config option (#1326) + +## 💡 Enhancements 💡 + +- Added disk merged (#1267) and process count (#1268) metrics to `hostmetrics` +- Log metric data points in `logging` exporter (#1258) +- Changed the `batch` processor to not ignore the errors returned by the exporters (#1259) +- Build and publish MSI (#1153) and DEB/RPM packages (#1278, #1335) +- Added batch size metric to `batch` processor (#1241) +- Added log support for `memorylimiter` processor (#1291) and `logging` exporter (#1298) +- Always add tags for `observability`, other metrics may use them (#1312) +- Added metrics support (#1313) and allow partial retries in `queued_retry` processor (#1297) +- Update `resource` processor: introduce `attributes` config parameter to specify actions on attributes similar to `attributes` processor, old config interface is deprecated (#1315) +- Update memory state labels for non-Linux OSs (#1325) +- Ensure tcp connection value is provided for all states, even when count is 0 (#1329) +- Set `batch` processor channel size to num cpus (#1330) +- Add `send_batch_max_size` config parameter to `batch` processor enforcing hard limit on batch size (#1310) +- Add support for including a per-RPC authentication to gRPC settings (#1250) + +## 🧰 Bug fixes 🧰 + +- Fixed OTLP waitForReady, not set from config (#1254) +- Fixed all translation diffs between OTLP and Jaeger (#1222) +- Disabled `process` scraper for any non Linux/Windows OS (#1328) + +## v0.5.0 Beta + +## 🛑 Breaking changes 🛑 + +- **Update OTLP to v0.4.0 (#1142)**: Collector will be incompatible with any other sender or receiver of OTLP protocol +of different versions +- Make "--new-metrics" command line flag the default (#1148) +- Change `endpoint` to `url` in Zipkin exporter config (#1186) +- Change `tls_credentials` to `tls_settings` in Jaegar receiver config (#1233) +- OTLP receiver config change for `protocols` to support mTLS (#1223) +- Remove `export_resource_labels` flag from Zipkin exporter (#1163) + +## 🚀 New components 🚀 + +- Receivers + - Added process scraper to the `hostmetrics` receiver (#1047) + +## 💡 Enhancements 💡 + +- otlpexporter: send configured headers in request (#1130) +- Enable Collector to be run as a Windows service (#1120) +- Add config for HttpServer (#1196) +- Allow cors in HTTPServerSettings (#1211) +- Add a generic grpc server settings config, cleanup client config (#1183) +- Rely on gRPC to batch and loadbalance between connections instead of custom logic (#1212) +- Allow to tune the read/write buffers for gRPC clients (#1213) +- Allow to tune the read/write buffers for gRPC server (#1218) + +## 🧰 Bug fixes 🧰 + +- Handle overlapping metrics from different jobs in prometheus exporter (#1096) +- Fix handling of SpanKind INTERNAL in OTLP OC translation (#1143) +- Unify zipkin v1 and v2 annotation/tag parsing logic (#1002) +- mTLS: Add support to configure client CA and enforce ClientAuth (#1185) +- Fixed untyped Prometheus receiver bug (#1194) +- Do not embed ProtocolServerSettings in gRPC (#1210) +- Add Context to the missing CreateMetricsReceiver method (#1216) + +## v0.4.0 Beta + +Released 2020-06-16 + +## 🛑 Breaking changes 🛑 + +- `isEnabled` configuration option removed (#909) +- `thrift_tchannel` protocol moved from `jaeger` receiver to `jaeger_legacy` in contrib (#636) + +## ⚠️ Major changes ⚠️ + +- Switch from `localhost` to `0.0.0.0` by default for all receivers (#1006) +- Internal API Changes (only impacts contributors) + - Add context to `Start` and `Stop` methods in the component (#790) + - Rename `AttributeValue` and `AttributeMap` method names (#781) +(other breaking changes in the internal trace data types) + - Change entire repo to use the new vanityurl go.opentelemetry.io/collector (#977) + +## 🚀 New components 🚀 + +- Receivers + - `hostmetrics` receiver with CPU (#862), disk (#921), load (#974), filesystem (#926), memory (#911), network (#930), and virtual memory (#989) support +- Processors + - `batch` for batching received metrics (#1060) + - `filter` for filtering (dropping) received metrics (#1001) + +## 💡 Enhancements 💡 + +- `otlp` receiver implement HTTP X-Protobuf (#1021) +- Exporters: Support mTLS in gRPC exporters (#927) +- Extensions: Add `zpages` for service (servicez, pipelinez, extensions) (#894) + +## 🧰 Bug fixes 🧰 + +- Add missing logging for metrics at `debug` level (#1108) +- Fix setting internal status code in `jaeger` receivers (#1105) +- `zipkin` export fails on span without timestamp when used with `queued_retry` (#1068) +- Fix `zipkin` receiver status code conversion (#996) +- Remove extra send/receive annotations with using `zipkin` v1 (#960) +- Fix resource attribute mutation bug when exporting in `jaeger` proto (#907) +- Fix metric/spans count, add tests for nil entries in the slices (#787) + + +## 🧩 Components 🧩 + +### Traces + +| Receivers | Processors | Exporters | +|:----------:|:-----------:|:----------:| +| Jaeger | Attributes | File | +| OpenCensus | Batch | Jaeger | +| OTLP | Memory Limiter | Logging | +| Zipkin | Queued Retry | OpenCensus | +| | Resource | OTLP | +| | Sampling | Zipkin | +| | Span || + +### Metrics + +| Receivers | Processors | Exporters | +|:----------:|:-----------:|:----------:| +| HostMetrics | Batch | File | +| OpenCensus | Filter | Logging | +| OTLP | Memory Limiter | OpenCensus | +| Prometheus || OTLP | +| VM Metrics || Prometheus | + +### Extensions + +- Health Check +- Performance Profiler +- zPages + + +## v0.3.0 Beta + +Released 2020-03-30 + +### Breaking changes + +- Make prometheus receiver config loading strict. #697 +Prometheus receiver will now fail fast if the config contains unused keys in it. + +### Changes and fixes + +- Enable best effort serve by default of Prometheus Exporter (https://github.com/orijtech/prometheus-go-metrics-exporter/pull/6) +- Fix null pointer exception in the logging exporter #743 +- Remove unnecessary condition to have at least one processor #744 + +### Components + +| Receivers / Exporters | Processors | Extensions | +|:---------------------:|:-----------:|:-----------:| +| Jaeger | Attributes | Health Check | +| OpenCensus | Batch | Performance Profiler | +| OpenTelemetry | Memory Limiter | zPages | +| Zipkin | Queued Retry | | +| | Resource | | +| | Sampling | | +| | Span | | + + +## v0.2.8 Alpha + +Alpha v0.2.8 of OpenTelemetry Collector + +- Implemented OTLP receiver and exporter. +- Added ability to pass config to the service programmatically (useful for custom builds). +- Improved own metrics / observability. +- Refactored component and factory interface definitions (breaking change #683) + + +## v0.2.7 Alpha + +Alpha v0.2.7 of OpenTelemetry Collector + +- Improved error handling on shutdown +- Partial implementation of new metrics (new obsreport package) +- Include resource labels for Zipkin exporter +- New `HASH` action to attribute processor + + + +## v0.2.6 Alpha + +Alpha v0.2.6 of OpenTelemetry Collector. +- Update metrics prefix to `otelcol` and expose command line argument to modify the prefix value. +- Extend Span processor to have include/exclude span logic. +- Batch dropped span now emits zero when no spans are dropped. + + +## v0.2.5 Alpha + +Alpha v0.2.5 of OpenTelemetry Collector. + +- Regexp-based filtering of spans based on service names. +- Ability to choose strict or regexp matching for include/exclude filters. + + +## v0.2.4 Alpha + +Alpha v0.2.4 of OpenTelemetry Collector. + +- Regexp-based filtering of span names. +- Ability to extract attributes from span names and rename span. +- File exporter for debugging. +- Span processor is now enabled by default. + + +## v0.2.3 Alpha + +Alpha v0.2.3 of OpenTelemetry Collector. + +Changes: +21a70d6 Add a memory limiter processor (#498) +9778b16 Refactor Jaeger Receiver config (#490) +ec4ad0c Remove workers from OpenCensus receiver implementation (#497) +4e01fa3 Update k8s config to use opentelemetry docker image and configuration (#459) + + +## v0.2.2 Alpha + +Alpha v0.2.2 of OpenTelemetry Collector. + +Main changes visible to users since previous release: + +- Improved Testbed and added more E2E tests. +- Made component interfaces more uniform (this is a breaking change). + +Note: v0.2.1 never existed and is skipped since it was tainted in some dependencies. + + +## v0.2.0 Alpha + +Alpha v0.2 of OpenTelemetry Collector. + +Docker image: omnition/opentelemetry-collector:v0.2.0 (we are working on getting this under an OpenTelemetry org) + +Main changes visible to users since previous release: + +* Rename from `service` to `collector`, the binary is now named `otelcol` + +* Configuration reorganized and using strict mode + +* Concurrency issues for pipelines transforming data addressed + +Commits: + +```terminal +0e505d5 Refactor config: pipelines now under service (#376) +402b80c Add Capabilities to Processor and use for Fanout cloning decision (#374) +b27d824 Use strict mode to read config (#375) +d769eb5 Fix concurrency handling when data is fanned out (#367) +dc6b290 Rename all github paths from opentelemtry-service to opentelemetry-collector (#371) +d038801 Rename otelsvc to otelcol (#365) +c264e0e Add Include/Exclude logic for Attributes Processor (#363) +8ce427a Pin a commit for Prometheus dependency in go.mod (#364) +2393774 Bump Jaeger version to 1.14.0 (latest) (#349) +63362d5 Update testbed modules (#360) +c0e2a27 Change dashes to underscores to separate words in config files (#357) +7609eaa Rename OpenTelemetry Service to Collector in docs and comments (#354) +bc5b299 Add common gRPC configuration settings (#340) +b38505c Remove network access popups on macos (#348) +f7727d1 Fixed loop variable pointer bug in jaeger translator (#341) +958beed Ensure that ConsumeMetricsData() is not passed empty metrics in the Prometheus receiver (#345) +0be295f Change log statement in Prometheus receiver from info to debug. (#344) +d205393 Add Owais to codeowners (#339) +8fa6afe Translate OC resource labels to Jaeger process tags (#325) +``` + + +## v0.0.2 Alpha + +Alpha release of OpenTelemetry Service. + +Docker image: omnition/opentelemetry-service:v0.0.2 (we are working on getting this under an OpenTelemetry org) + +Main changes visible to users since previous release: + +```terminal +8fa6afe Translate OC resource labels to Jaeger process tags (#325) +047b0f3 Allow environment variables in config (#334) +96c24a3 Add exclude/include spans option to attributes processor (#311) +4db0414 Allow metric processors to be specified in pipelines (#332) +c277569 Add observability instrumentation for Prometheus receiver (#327) +f47aa79 Add common configuration for receiver tls (#288) +a493765 Refactor extensions to new config format (#310) +41a7afa Add Span Processor logic +97a71b3 Use full name for the metrics and spans created for observability (#316) +fed4ed2 Add support to record metrics for metricsexporter (#315) +5edca32 Add include_filter configuration to prometheus receiver (#298) +0068d0a Passthrough CORS allowed origins (#260) +``` + + +## v0.0.1 Alpha + +This is the first alpha release of OpenTelemetry Service. + +Docker image: omnition/opentelemetry-service:v0.0.1 + + +[v0.3.0]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.10...v0.3.0 +[v0.2.10]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.8...v0.2.10 +[v0.2.8]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.7...v0.2.8 +[v0.2.7]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.6...v0.2.7 +[v0.2.6]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.5...v0.2.6 +[v0.2.5]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.4...v0.2.5 +[v0.2.4]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.3...v0.2.4 +[v0.2.3]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.2...v0.2.3 +[v0.2.2]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.0...v0.2.2 +[v0.2.0]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.0.2...v0.2.0 +[v0.0.2]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.0.1...v0.0.2 +[v0.0.1]: https://github.com/open-telemetry/opentelemetry-collector/tree/v0.0.1 diff --git a/internal/otel_collector/CONTRIBUTING.md b/internal/otel_collector/CONTRIBUTING.md new file mode 100644 index 00000000000..34c4cb22cea --- /dev/null +++ b/internal/otel_collector/CONTRIBUTING.md @@ -0,0 +1,289 @@ +# Contributing Guide + +We'd love your help! + +## How to structure PRs to get expedient reviews? + +We recommend that any PR (unless it is trivial) to be smaller than 500 lines (excluding go mod/sum changes) in order to help reviewers to do a thorough and reasonably fast reviews. + +### When adding a new component + +Consider submitting different PRs for (more details about adding new components [here](#adding-new-components)) : + +* First PR should include the overall structure of the new component: + * Readme, configuration, and factory implementation usually using the helper factory structs. + * This PR is usually trivial to review, so the size limit does not apply to it. +* Second PR should include the concrete implementation of the component. +If the size of this PR is larger than the recommended size consider splitting it in multiple PRs. +* Last PR should enable the new component and add it to the `otelcontribcol` binary by updating the `components.go` file. +The component must be enabled only after sufficient testing, and there is enough confidence in the stability and quality of the component. + +### Refactoring Work + +Any refactoring work must be split in its own PR that does not include any behavior changes. +It is important to do this to avoid hidden changes in large and trivial refactoring PRs. + +## Report a bug or requesting feature + +Reporting bugs is an important contribution. Please make sure to include: + +* Expected and actual behavior +* OpenTelemetry version you are running +* If possible, steps to reproduce + +## How to contribute + +### Before you start + +Please read project contribution +[guide](https://github.com/open-telemetry/community/blob/master/CONTRIBUTING.md) +for general practices for OpenTelemetry project. + +Select a good issue from the links below (ordered by difficulty/complexity): + +* [Good First Issue](https://github.com/open-telemetry/opentelemetry-collector/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) +* [Up for Grabs](https://github.com/open-telemetry/opentelemetry-collector/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs+) +* [Help Wanted](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) + +Comment on the issue that you want to work on so we can assign it to you and +clarify anything related to it. + +If you would like to work on something that is not listed as an issue +(e.g. a new feature or enhancement) please first read our [vision](docs/vision.md) and +[roadmap](docs/roadmap.md) to make sure your proposal aligns with the goals of the +Collector, then create an issue and describe your proposal. It is best to do this +in advance so that maintainers can decide if the proposal is a good fit for +this repository. This will help avoid situations when you spend significant time +on something that maintainers may decide this repo is not the right place for. + +Follow the instructions below to create your PR. + +### Fork + +In the interest of keeping this repository clean and manageable, you should +work from a fork. To create a fork, click the 'Fork' button at the top of the +repository, then clone the fork locally using `git clone +git@github.com:USERNAME/opentelemetry-service.git`. + +You should also add this repository as an "upstream" repo to your local copy, +in order to keep it up to date. You can add this as a remote like so: + +`git remote add upstream https://github.com/open-telemetry/opentelemetry-collector.git` + +Verify that the upstream exists: + +`git remote -v` + +To update your fork, fetch the upstream repo's branches and commits, then merge your master with upstream's master: + +``` +git fetch upstream +git checkout master +git merge upstream/master +``` + +Remember to always work in a branch of your local copy, as you might otherwise +have to contend with conflicts in master. + +Please also see [GitHub +workflow](https://github.com/open-telemetry/community/blob/master/CONTRIBUTING.md#github-workflow) +section of general project contributing guide. + +## Required Tools + +Working with the project sources requires the following tools: + +1. [git](https://git-scm.com/) +2. [go](https://golang.org/) (version 1.14 and up) +3. [make](https://www.gnu.org/software/make/) +4. [docker](https://www.docker.com/) + +## Repository Setup + +Fork the repo, checkout the upstream repo to your GOPATH by: + +``` +$ GO111MODULE="" go get -d go.opentelemetry.io/collector +``` + +Add your fork as an origin: + +```shell +$ cd $(go env GOPATH)/src/go.opentelemetry.io/collector +$ git remote add fork git@github.com:YOUR_GITHUB_USERNAME/opentelemetry-service.git +``` + +Run tests, fmt and lint: + +```shell +$ make install-tools # Only first time. +$ make +``` + +*Note:* the default build target requires tools that are installed at `$(go env GOPATH)/bin`, ensure that `$(go env GOPATH)/bin` is included in your `PATH`. + +## Creating a PR + +Checkout a new branch, make modifications, build locally, and push the branch to your fork +to open a new PR: + +```shell +$ git checkout -b feature +# edit +$ make +$ make fmt +$ git commit +$ git push fork feature +``` + +## General Notes + +This project uses Go 1.14.* and CircleCI. + +CircleCI uses the Makefile with the `ci` target, it is recommended to +run it before submitting your PR. It runs `gofmt -s` (simplify) and `golint`. + +The dependencies are managed with `go mod` if you work with the sources under your +`$GOPATH` you need to set the environment variable `GO111MODULE=on`. + +## Coding Guidelines + +Although OpenTelemetry project as a whole is still in Alpha stage we consider +OpenTelemetry Collector to be close to production quality and the quality bar +for contributions is set accordingly. Contributions must have readable code written +with maintainability in mind (if in doubt check [Effective Go](https://golang.org/doc/effective_go.html) +for coding advice). The code must adhere to the following robustness principles that +are important for software that runs autonomously and continuously without direct +interaction with a human (such as this Collector). + +### Startup Error Handling + +Verify configuration during startup and fail fast if the configuration is invalid. +This will bring the attention of a human to the problem as it is more typical for humans +to notice problems when the process is starting as opposed to problems that may arise +sometime (potentially long time) after process startup. Monitoring systems are likely +to automatically flag processes that exit with failure during startup, making it +easier to notice the problem. The Collector should print a reasonable log message to +explain the problem and exit with a non-zero code. It is acceptable to crash the process +during startup if there is no good way to exit cleanly but do your best to log and +exit cleanly with a process exit code. + +### Propagate Errors to the Caller + +Do not crash or exit outside the `main()` function, e.g. via `log.Fatal` or `os.Exit`, +even during startup. Instead, return detailed errors to be handled appropriately +by the caller. The code in packages other than `main` may be imported and used by +third-party applications, and they should have full control over error handling +and process termination. + +### Do not Crash after Startup + +Do not crash or exit the Collector process after the startup sequence is finished. +A running Collector typically contains data that is received but not yet exported further +(e.g. is stored in the queues and other processors). Crashing or exiting the Collector +process will result in losing this data since typically the receiver has +already acknowledged the receipt for this data and the senders of the data will +not send that data again. + +### Bad Input Handling + +Do not crash on bad input in receivers or elsewhere in the pipeline. +[Crash-only software](https://en.wikipedia.org/wiki/Crash-only_software) +is valid in certain cases; however, this is not a correct approach for Collector (except +during startup, see above). The reason is that many senders from which Collector +receives data have built-in automatic retries of the _same_ data if no +acknowledgment is received from the Collector. If you crash on bad input +chances are high that after the Collector is restarted it will see the same +data in the input and will crash again. This will likely result in infinite +crashing loop if you have automatic retries in place. + +Typically bad input when detected in a receiver should be reported back to the +sender. If it is elsewhere in the pipeline it may be too late to send a response +to the sender (particularly in processors which are not synchronously processing +data). In either case it is recommended to keep a metric that counts bad input data. + +### Error Handling and Retries + +Be rigorous in error handling. Don't ignore errors. Think carefully about each +error and decide if it is a fatal problem or a transient problem that may go away +when retried. Fatal errors should be logged or recorded in an internal metric to +provide visibility to users of the Collector. For transient errors come up with a +retrying strategy and implement it. Typically you will +want to implement retries with some sort of exponential back-off strategy. For +connection or sending retries use jitter for back-off intervals to avoid overwhelming +your destination when network is restored or the destination is recovered. +[Exponential Backoff](https://github.com/cenkalti/backoff) is a good library that +provides all this functionality. + +### Logging + +Log your component startup and shutdown, including successful outcomes (but don't +overdo it, keep the number of success message to a minimum). +This can help to understand the context of failures if they occur elsewhere after +your code is successfully executed. + +Use logging carefully for events that can happen frequently to avoid flooding +the logs. Avoid outputting logs per a received or processed data item since this can +amount to very large number of log entries (Collector is designed to process +many thousands of spans and metrics per second). For such high-frequency events +instead of logging consider adding an internal metric and increment it when +the event happens. + +Make log message human readable and also include data that is needed for easier +understanding of what happened and in what context. + +### Resource Usage + +Limit usage of CPU, RAM or other resources that the code can use. Do not write code +that consumes resources in an uncontrolled manner. For example if you have a queue +that can contain unprocessed messages always limit the size of the queue unless you +have other ways to guarantee that the queue will be consumed faster than items are +added to it. + +Performance test the code for both normal use-cases under acceptable load and also for +abnormal use-cases when the load exceeds acceptable many times. Ensure that +your code performs predictably under abnormal use. For example if the code +needs to process received data and cannot keep up with the receiving rate it is +not acceptable to keep allocating more memory for received data until the Collector +runs out of memory. Instead have protections for these situations, e.g. when hitting +resource limits drop the data and record the fact that it was dropped in a metric +that is exposed to users. + +### Graceful Shutdown + +Collector does not yet support graceful shutdown but we plan to add it. All components +must be ready to shutdown gracefully via `Shutdown()` function that all component +interfaces require. If components contain any temporary data they need to process +and export it out of the Collector before shutdown is completed. The shutdown process +will have a maximum allowed duration so put a limit on how long your shutdown +operation can take. + +### Unit Tests + +Cover important functionality with unit tests. We require that contributions +do not decrease overall code coverage of the codebase - this is aligned with our +goal to increase coverage over time. Keep track of execution time for your unit +tests and try to keep them as short as possible. + +## End-to-end Tests + +If you implement a new component add end-to-end tests for the component using +the automated [Testbed](testbed/README.md). + +## Release + +See [release](docs/release.md) for details. + +## Common Issues + +Build fails due to dependency issues, e.g. + +```sh +go: github.com/golangci/golangci-lint@v1.31.0 requires + github.com/tommy-muehle/go-mnd@v1.3.1-0.20200224220436-e6f9a994e8fa: invalid pseudo-version: git fetch --unshallow -f origin in /root/go/pkg/mod/cache/vcs/053b1e985f53e43f78db2b3feaeb7e40a2ae482c92734ba3982ca463d5bf19ce: exit status 128: + fatal: git fetch-pack: expected shallow list + ``` + +`go env GOPROXY` should return `https://proxy.golang.org,direct`. If it does not, set it as an environment variable: + +`export GOPROXY=https://proxy.golang.org,direct` diff --git a/internal/otel_collector/LICENSE b/internal/otel_collector/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/internal/otel_collector/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/internal/otel_collector/Makefile b/internal/otel_collector/Makefile new file mode 100644 index 00000000000..ecb6d92b8fa --- /dev/null +++ b/internal/otel_collector/Makefile @@ -0,0 +1,309 @@ +include ./Makefile.Common + +# This is the code that we want to run checklicense, staticcheck, etc. +ALL_SRC := $(shell find . -name '*.go' \ + -not -path './cmd/issuegenerator/*' \ + -not -path './cmd/mdatagen/*' \ + -not -path './internal/tools/*' \ + -not -path './examples/demo/app/*' \ + -not -path './internal/data/opentelemetry-proto-gen/*' \ + -type f | sort) + +# ALL_PKGS is the list of all packages where ALL_SRC files reside. +ALL_PKGS := $(shell go list $(sort $(dir $(ALL_SRC)))) + +# All source code and documents. Used in spell check. +ALL_DOC := $(shell find . \( -name "*.md" -o -name "*.yaml" \) \ + -type f | sort) + +# ALL_MODULES includes ./* dirs (excludes . dir) +ALL_MODULES := $(shell find . -type f -name "go.mod" -exec dirname {} \; | sort | egrep '^./' ) + +CMD?= +TOOLS_MOD_DIR := ./internal/tools + +GOOS=$(shell go env GOOS) +GOARCH=$(shell go env GOARCH) +# BUILD_TYPE should be one of (dev, release). +BUILD_TYPE?=release + +GIT_SHA=$(shell git rev-parse --short HEAD) +BUILD_INFO_IMPORT_PATH=go.opentelemetry.io/collector/internal/version +BUILD_X1=-X $(BUILD_INFO_IMPORT_PATH).GitHash=$(GIT_SHA) +ifdef VERSION +BUILD_X2=-X $(BUILD_INFO_IMPORT_PATH).Version=$(VERSION) +endif +BUILD_X3=-X $(BUILD_INFO_IMPORT_PATH).BuildType=$(BUILD_TYPE) +BUILD_INFO=-ldflags "${BUILD_X1} ${BUILD_X2} ${BUILD_X3}" + +RUN_CONFIG?=examples/local/otel-config.yaml + +CONTRIB_PATH=$(CURDIR)/../opentelemetry-collector-contrib + +# Function to execute a command. Note the empty line before endef to make sure each command +# gets executed separately instead of concatenated with previous one. +# Accepts command to execute as first parameter. +define exec-command +$(1) + +endef + +.DEFAULT_GOAL := all + +.PHONY: all +all: gochecklicense goimpi golint gomisspell gotest otelcol + +all-modules: + @echo $(ALL_MODULES) | tr ' ' '\n' | sort + +.PHONY: testbed-loadtest +testbed-loadtest: otelcol + cd ./testbed/tests && ./runtests.sh + +.PHONY: testbed-correctness +testbed-correctness: otelcol + cd ./testbed/correctness/traces && ./runtests.sh + +.PHONY: testbed-list-loadtest +testbed-list-loadtest: + RUN_TESTBED=1 $(GOTEST) -v ./testbed/tests --test.list '.*'| grep "^Test" + +.PHONY: testbed-list-correctness +testbed-list-correctness: + RUN_TESTBED=1 $(GOTEST) -v ./testbed/correctness --test.list '.*'| grep "^Test" + +.PHONY: gotest +gotest: + @$(MAKE) for-all CMD="make test" + +.PHONY: gobenchmark +gobenchmark: + @$(MAKE) for-all CMD="make benchmark" + +.PHONY: gotest-with-cover +gotest-with-cover: + @echo pre-compiling tests + @time $(GOTEST) -i ./... + $(GO_ACC) ./... + go tool cover -html=coverage.txt -o coverage.html + +.PHONY: goaddlicense +goaddlicense: + @$(MAKE) for-all CMD="make addlicense" + +.PHONY: gochecklicense +gochecklicense: + @$(MAKE) for-all CMD="make checklicense" + +.PHONY: gomisspell +gomisspell: + @$(MAKE) for-all CMD="make misspell" + +.PHONY: gomisspell-correction +gomisspell-correction: + @$(MAKE) for-all CMD="make misspell-correction" + +.PHONY: golint +golint: + @$(MAKE) for-all CMD="make lint" + +.PHONY: goimpi +goimpi: + @$(MAKE) for-all CMD="make impi" + +.PHONY: gofmt +gofmt: + @$(MAKE) for-all CMD="make fmt" + +.PHONY: gotidy +gotidy: + $(MAKE) for-all CMD="rm -fr go.sum" + $(MAKE) for-all CMD="go mod tidy" + +.PHONY: install-tools +install-tools: + cd $(TOOLS_MOD_DIR) && go install github.com/client9/misspell/cmd/misspell + cd $(TOOLS_MOD_DIR) && go install github.com/golangci/golangci-lint/cmd/golangci-lint + cd $(TOOLS_MOD_DIR) && go install github.com/google/addlicense + cd $(TOOLS_MOD_DIR) && go install github.com/jstemmer/go-junit-report + cd $(TOOLS_MOD_DIR) && go install github.com/mjibson/esc + cd $(TOOLS_MOD_DIR) && go install github.com/ory/go-acc + cd $(TOOLS_MOD_DIR) && go install github.com/pavius/impi/cmd/impi + cd $(TOOLS_MOD_DIR) && go install github.com/securego/gosec/v2/cmd/gosec + cd $(TOOLS_MOD_DIR) && go install github.com/tcnksm/ghr + cd $(TOOLS_MOD_DIR) && go install golang.org/x/tools/cmd/goimports + cd $(TOOLS_MOD_DIR) && go install honnef.co/go/tools/cmd/staticcheck + cd cmd/mdatagen && go install ./ + +.PHONY: otelcol +otelcol: + go generate ./... + GO111MODULE=on CGO_ENABLED=0 go build -o ./bin/otelcol_$(GOOS)_$(GOARCH)$(EXTENSION) $(BUILD_INFO) ./cmd/otelcol + +.PHONY: run +run: + GO111MODULE=on go run --race ./cmd/otelcol/... --config ${RUN_CONFIG} + +.PHONY: docker-component # Not intended to be used directly +docker-component: check-component + GOOS=linux $(MAKE) $(COMPONENT) + cp ./bin/$(COMPONENT)_linux_amd64 ./cmd/$(COMPONENT)/$(COMPONENT) + docker build -t $(COMPONENT) ./cmd/$(COMPONENT)/ + rm ./cmd/$(COMPONENT)/$(COMPONENT) + +.PHONY: for-all +for-all: + @echo "running $${CMD} in root" + @$${CMD} + @set -e; for dir in $(ALL_MODULES); do \ + (cd "$${dir}" && \ + echo "running $${CMD} in $${dir}" && \ + $${CMD} ); \ + done + +.PHONY: check-component +check-component: +ifndef COMPONENT + $(error COMPONENT variable was not defined) +endif + +.PHONY: add-tag +add-tag: + @[ "${TAG}" ] || ( echo ">> env var TAG is not set"; exit 1 ) + @echo "Adding tag ${TAG}" + @git tag -a ${TAG} -s -m "Version ${TAG}" + +.PHONY: delete-tag +delete-tag: + @[ "${TAG}" ] || ( echo ">> env var TAG is not set"; exit 1 ) + @echo "Deleting tag ${TAG}" + @git tag -d ${TAG} + +.PHONY: docker-otelcol +docker-otelcol: + COMPONENT=otelcol $(MAKE) docker-component + +.PHONY: binaries +binaries: otelcol + +.PHONY: binaries-all-sys +binaries-all-sys: binaries-darwin_amd64 binaries-linux_amd64 binaries-linux_arm64 binaries-windows_amd64 + +.PHONY: binaries-darwin_amd64 +binaries-darwin_amd64: + GOOS=darwin GOARCH=amd64 $(MAKE) binaries + +.PHONY: binaries-linux_amd64 +binaries-linux_amd64: + GOOS=linux GOARCH=amd64 $(MAKE) binaries + +.PHONY: binaries-linux_arm64 +binaries-linux_arm64: + GOOS=linux GOARCH=arm64 $(MAKE) binaries + +.PHONY: binaries-windows_amd64 +binaries-windows_amd64: + GOOS=windows GOARCH=amd64 EXTENSION=.exe $(MAKE) binaries + +.PHONY: deb-rpm-package +%-package: ARCH ?= amd64 +%-package: + $(MAKE) binaries-linux_$(ARCH) + docker build -t otelcol-fpm internal/buildscripts/packaging/fpm + docker run --rm -v $(CURDIR):/repo -e PACKAGE=$* -e VERSION=$(VERSION) -e ARCH=$(ARCH) otelcol-fpm + +.PHONY: genmdata +genmdata: + $(MAKE) for-all CMD="go generate ./..." + +# Definitions for ProtoBuf generation. + +# The source directory for OTLP ProtoBufs. +OPENTELEMETRY_PROTO_SRC_DIR=internal/data/opentelemetry-proto + +# Find all .proto files. +OPENTELEMETRY_PROTO_FILES := $(subst $(OPENTELEMETRY_PROTO_SRC_DIR)/,,$(wildcard $(OPENTELEMETRY_PROTO_SRC_DIR)/opentelemetry/proto/*/v1/*.proto $(OPENTELEMETRY_PROTO_SRC_DIR)/opentelemetry/proto/collector/*/v1/*.proto)) + +# Target directory to write generated files to. +PROTO_TARGET_GEN_DIR=internal/data/opentelemetry-proto-gen + +# Go package name to use for generated files. +PROTO_PACKAGE=go.opentelemetry.io/collector/$(PROTO_TARGET_GEN_DIR) + +# Intermediate directory used during generation. +PROTO_INTERMEDIATE_DIR=internal/data/.patched-otlp-proto + +DOCKER_PROTOBUF ?= otel/build-protobuf:0.1.0 +PROTOC := docker run --rm -u ${shell id -u} -v${PWD}:${PWD} -w${PWD}/$(PROTO_INTERMEDIATE_DIR) ${DOCKER_PROTOBUF} --proto_path=${PWD} +PROTO_INCLUDES := -I/usr/include/github.com/gogo/protobuf -I./ + +# Generate OTLP Protobuf Go files. This will place generated files in PROTO_TARGET_GEN_DIR. +genproto: + git submodule update --init + # Call a sub-make to ensure OPENTELEMETRY_PROTO_FILES is populated after the submodule + # files are present. + $(MAKE) genproto_sub + $(MAKE) fmt + +genproto_sub: + @echo Generating code for the following files: + @$(foreach file,$(OPENTELEMETRY_PROTO_FILES),$(call exec-command,echo $(file))) + + @echo Delete intermediate directory. + @rm -rf $(PROTO_INTERMEDIATE_DIR) + + @echo Copy .proto file to intermediate directory. + mkdir -p $(PROTO_INTERMEDIATE_DIR)/opentelemetry + cp -R $(OPENTELEMETRY_PROTO_SRC_DIR)/opentelemetry/* $(PROTO_INTERMEDIATE_DIR)/opentelemetry + + # Patch proto files. See proto_patch.sed for patching rules. + @echo Modify them in the intermediate directory. + $(foreach file,$(OPENTELEMETRY_PROTO_FILES),$(call exec-command,sed -f proto_patch.sed $(OPENTELEMETRY_PROTO_SRC_DIR)/$(file) > $(PROTO_INTERMEDIATE_DIR)/$(file))) + + @echo Generate Go code from .proto files in intermediate directory. + $(foreach file,$(OPENTELEMETRY_PROTO_FILES),$(call exec-command,$(PROTOC) $(PROTO_INCLUDES) --gogofaster_out=plugins=grpc:./ $(file))) + + @echo Generate gRPC gateway code. + $(PROTOC) $(PROTO_INCLUDES) --grpc-gateway_out=logtostderr=true,grpc_api_configuration=opentelemetry/proto/collector/trace/v1/trace_service_http.yaml:./ opentelemetry/proto/collector/trace/v1/trace_service.proto + $(PROTOC) $(PROTO_INCLUDES) --grpc-gateway_out=logtostderr=true,grpc_api_configuration=opentelemetry/proto/collector/metrics/v1/metrics_service_http.yaml:./ opentelemetry/proto/collector/metrics/v1/metrics_service.proto + $(PROTOC) $(PROTO_INCLUDES) --grpc-gateway_out=logtostderr=true,grpc_api_configuration=opentelemetry/proto/collector/logs/v1/logs_service_http.yaml:./ opentelemetry/proto/collector/logs/v1/logs_service.proto + + @echo Move generated code to target directory. + mkdir -p $(PROTO_TARGET_GEN_DIR) + cp -R $(PROTO_INTERMEDIATE_DIR)/$(PROTO_PACKAGE)/* $(PROTO_TARGET_GEN_DIR)/ + rm -rf $(PROTO_INTERMEDIATE_DIR)/go.opentelemetry.io + + @rm -rf $(OPENTELEMETRY_PROTO_SRC_DIR)/* + @rm -rf $(OPENTELEMETRY_PROTO_SRC_DIR)/.* > /dev/null 2>&1 || true + +# Generate structs, functions and tests for pdata package. Must be used after any changes +# to proto and after running `make genproto` +genpdata: + go run cmd/pdatagen/main.go + $(MAKE) fmt + +# Checks that the HEAD of the contrib repo checked out in CONTRIB_PATH compiles +# against the current version of this repo. +.PHONY: check-contrib +check-contrib: + @echo Setting contrib at $(CONTRIB_PATH) to use this core checkout + make -C $(CONTRIB_PATH) for-all CMD="go mod edit -replace go.opentelemetry.io/collector=$(CURDIR)" + make -C $(CONTRIB_PATH) test + @echo Restoring contrib to no longer use this core checkout + make -C $(CONTRIB_PATH) for-all CMD="go mod edit -dropreplace go.opentelemetry.io/collector" + +# List of directories where certificates are stored for unit tests. +CERT_DIRS := config/configgrpc/testdata \ + config/confighttp/testdata \ + receiver/jaegerreceiver/testdata \ + exporter/jaegerexporter/testdata + +# Generate certificates for unit tests relying on certificates. +.PHONY: certs +certs: + $(foreach dir, $(CERT_DIRS), $(call exec-command, @internal/buildscripts/gen-certs.sh -o $(dir))) + +# Generate certificates for unit tests relying on certificates without copying certs to specific test directories. +.PHONY: certs-dryrun +certs-dryrun: + @internal/buildscripts/gen-certs.sh -d diff --git a/internal/otel_collector/Makefile.Common b/internal/otel_collector/Makefile.Common new file mode 100644 index 00000000000..cfa9892c93b --- /dev/null +++ b/internal/otel_collector/Makefile.Common @@ -0,0 +1,94 @@ +# SRC_ROOT is the top of the source tree. +SRC_ROOT := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))) + +ALL_SRC := $(shell find . -name '*.go' \ + -not -path '*/third_party/*' \ + -type f | sort) + +# ALL_PKGS is the list of all packages where ALL_SRC files reside. +ALL_PKGS := $(shell go list ./...) + +# All source code and documents. Used in spell check. +ALL_DOC := $(shell find . \( -name "*.md" -o -name "*.yaml" \) \ + -type f | sort) + +GOTEST_OPT?= -v -race -timeout 180s +GO_ACC=go-acc +GOTEST=go test +ADDLICENCESE=addlicense +MISSPELL=misspell -error +MISSPELL_CORRECTION=misspell -w +STATIC_CHECK=staticcheck +LINT=golangci-lint +IMPI=impi + +all-license-srcs: + @echo $(ALL_SRC) | tr ' ' '\n' | sort + +.PHONY: test +test: + @echo "running go unit test ./... in `pwd`" + @echo $(ALL_PKGS) | xargs -n 10 $(GOTEST) $(GOTEST_OPT) + +.PHONY: benchmark +benchmark: + $(GOTEST) -bench=. -run=notests ./... + +.PHONY: addlicense +addlicense: + @ADDLICENCESEOUT=`$(ADDLICENCESE) -y "" -c "The OpenTelemetry Authors" $(ALL_SRC) 2>&1`; \ + if [ "$$ADDLICENCESEOUT" ]; then \ + echo "$(ADDLICENCESE) FAILED => add License errors:\n"; \ + echo "$$ADDLICENCESEOUT\n"; \ + exit 1; \ + else \ + echo "Add License finished successfully"; \ + fi + +.PHONY: checklicense +checklicense: + @ADDLICENCESEOUT=`$(ADDLICENCESE) -check $(ALL_SRC) 2>&1`; \ + if [ "$$ADDLICENCESEOUT" ]; then \ + echo "$(ADDLICENCESE) FAILED => add License errors:\n"; \ + echo "$$ADDLICENCESEOUT\n"; \ + echo "Use 'make addlicense' to fix this."; \ + exit 1; \ + else \ + echo "Check License finished successfully"; \ + fi + +.PHONY: fmt +fmt: + gofmt -w -s ./ + goimports -w -local go.opentelemetry.io/collector ./ + +.PHONY: lint-static-check +lint-static-check: + @STATIC_CHECK_OUT=`$(STATIC_CHECK) $(ALL_PKGS) 2>&1`; \ + if [ "$$STATIC_CHECK_OUT" ]; then \ + echo "$(STATIC_CHECK) FAILED => static check errors:\n"; \ + echo "$$STATIC_CHECK_OUT\n"; \ + exit 1; \ + else \ + echo "Static check finished successfully"; \ + fi + +.PHONY: lint +lint: lint-static-check + $(LINT) run --allow-parallel-runners + +.PHONY: misspell +misspell: +ifdef ALL_DOC + $(MISSPELL) $(ALL_DOC) +endif + +.PHONY: misspell-correction +misspell-correction: +ifdef ALL_DOC + $(MISSPELL_CORRECTION) $(ALL_DOC) +endif + +.PHONY: impi +impi: + @$(IMPI) --local go.opentelemetry.io/collector --scheme stdThirdPartyLocal ./... diff --git a/internal/otel_collector/README.md b/internal/otel_collector/README.md new file mode 100644 index 00000000000..e8340aef62c --- /dev/null +++ b/internal/otel_collector/README.md @@ -0,0 +1,90 @@ +--- + +

+ + Getting Started +   •   + Getting Involved +   •   + Getting In Touch + +

+ +

+ + Go Report Card + + + Build Status + + + Codecov Status + + + GitHub release (latest by date including pre-releases) + + Beta +

+ +

+ + Contributing +   •   + Vision +   •   + Design +   •   + Monitoring +   •   + Performance +   •   + Roadmap + +

+ +--- + +# OpenTelemetry Icon OpenTelemetry Collector + +The OpenTelemetry Collector offers a vendor-agnostic implementation on how to +receive, process and export telemetry data. In addition, it removes the need +to run, operate and maintain multiple agents/collectors in order to support +open-source telemetry data formats (e.g. Jaeger, Prometheus, etc.) sending to +multiple open-source or commercial back-ends. + +Objectives: + +- Usable: Reasonable default configuration, supports popular protocols, runs and collects out of the box. +- Performant: Highly stable and performant under varying loads and configurations. +- Observable: An exemplar of an observable service. +- Extensible: Customizable without touching the core code. +- Unified: Single codebase, deployable as an agent or collector with support for traces, metrics and logs. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). + +Triagers ([@open-telemetry/collector-triagers](https://github.com/orgs/open-telemetry/teams/collector-triager)): +- [Andrew Hsu](https://github.com/andrewhsu), Lightstep +- [Steve Flanders](https://github.com/flands), Splunk + +Approvers ([@open-telemetry/collector-approvers](https://github.com/orgs/open-telemetry/teams/collector-approvers)): + +- [Dmitrii Anoshin](https://github.com/dmitryax), Splunk +- [James Bebbington](https://github.com/james-bebbington), Google +- [Jay Camp](https://github.com/jrcamp), Splunk +- [Nail Islamov](https://github.com/nilebox), Google +- [Owais Lone](https://github.com/owais), Splunk + +Maintainers ([@open-telemetry/collector-maintainers](https://github.com/orgs/open-telemetry/teams/collector-maintainers)): + +- [Bogdan Drutu](https://github.com/BogdanDrutu), Splunk +- [Tigran Najaryan](https://github.com/tigrannajaryan), Splunk + +Learn more about roles in the [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md). + +Thanks to all the people who already contributed! + + + + diff --git a/internal/otel_collector/client/client.go b/internal/otel_collector/client/client.go new file mode 100644 index 00000000000..6b134a30abf --- /dev/null +++ b/internal/otel_collector/client/client.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package client contains generic representations of clients connecting to different receivers +package client + +import ( + "context" + "net" + "net/http" + + "google.golang.org/grpc/peer" +) + +type ctxKey struct{} + +// Client represents a generic client that sends data to any receiver supported by the OT receiver +type Client struct { + IP string +} + +// NewContext takes an existing context and derives a new context with the client value stored on it +func NewContext(ctx context.Context, c *Client) context.Context { + return context.WithValue(ctx, ctxKey{}, c) +} + +// FromContext takes a context and returns a Client value from it, if present. +func FromContext(ctx context.Context) (*Client, bool) { + c, ok := ctx.Value(ctxKey{}).(*Client) + return c, ok +} + +// FromGRPC takes a GRPC context and tries to extract client information from it +func FromGRPC(ctx context.Context) (*Client, bool) { + if p, ok := peer.FromContext(ctx); ok { + ip := parseIP(p.Addr.String()) + if ip != "" { + return &Client{ip}, true + } + } + return nil, false +} + +// FromHTTP takes a net/http Request object and tries to extract client information from it +func FromHTTP(r *http.Request) (*Client, bool) { + ip := parseIP(r.RemoteAddr) + if ip == "" { + return nil, false + } + return &Client{ip}, true +} + +func parseIP(source string) string { + ipstr, _, err := net.SplitHostPort(source) + if err == nil { + return ipstr + } + ip := net.ParseIP(source) + if ip != nil { + return ip.String() + } + return "" +} diff --git a/internal/otel_collector/client/client_test.go b/internal/otel_collector/client/client_test.go new file mode 100644 index 00000000000..2aaa13f4544 --- /dev/null +++ b/internal/otel_collector/client/client_test.go @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package client contains generic representations of clients connecting to different receivers +package client + +import ( + "context" + "net" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/peer" +) + +func TestClientContext(t *testing.T) { + ips := []string{ + "1.1.1.1", "127.0.0.1", "1111", "ip", + } + for _, ip := range ips { + ctx := NewContext(context.Background(), &Client{ip}) + c, ok := FromContext(ctx) + assert.True(t, ok) + assert.NotNil(t, c) + assert.Equal(t, c.IP, ip) + } +} + +func TestParsingGRPC(t *testing.T) { + grpcCtx := peer.NewContext(context.Background(), &peer.Peer{ + Addr: &net.TCPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 80, + }, + }) + + client, ok := FromGRPC(grpcCtx) + assert.True(t, ok) + assert.NotNil(t, client) + assert.Equal(t, client.IP, "192.168.1.1") +} + +func TestParsingHTTP(t *testing.T) { + client, ok := FromHTTP(&http.Request{RemoteAddr: "192.168.1.2"}) + assert.True(t, ok) + assert.NotNil(t, client) + assert.Equal(t, client.IP, "192.168.1.2") +} diff --git a/internal/otel_collector/cmd/otelcol/Dockerfile b/internal/otel_collector/cmd/otelcol/Dockerfile new file mode 100644 index 00000000000..4e99c435a63 --- /dev/null +++ b/internal/otel_collector/cmd/otelcol/Dockerfile @@ -0,0 +1,16 @@ +FROM alpine:3.12 as certs +RUN apk --update add ca-certificates + +FROM alpine:3.12 AS otelcol +COPY otelcol / +# Note that this shouldn't be necessary, but in some cases the file seems to be +# copied with the execute bit lost (see #1317) +RUN chmod 755 /otelcol + +FROM scratch +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=otelcol /otelcol / +COPY config.yaml /etc/otel/config.yaml +ENTRYPOINT ["/otelcol"] +CMD ["--config", "/etc/otel/config.yaml"] +EXPOSE 55678 55679 diff --git a/internal/otel_collector/cmd/otelcol/config.yaml b/internal/otel_collector/cmd/otelcol/config.yaml new file mode 100644 index 00000000000..ce9ba1a847a --- /dev/null +++ b/internal/otel_collector/cmd/otelcol/config.yaml @@ -0,0 +1,55 @@ +extensions: + health_check: + pprof: + endpoint: 0.0.0.0:1777 + zpages: + endpoint: 0.0.0.0:55679 + +receivers: + otlp: + protocols: + grpc: + http: + + opencensus: + + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: ['0.0.0.0:8888'] + + jaeger: + protocols: + grpc: + thrift_binary: + thrift_compact: + thrift_http: + + zipkin: + +processors: + batch: + +exporters: + logging: + logLevel: debug + +service: + + pipelines: + + traces: + receivers: [otlp, opencensus, jaeger, zipkin] + processors: [batch] + exporters: [logging] + + metrics: + receivers: [otlp, opencensus, prometheus] + processors: [batch] + exporters: [logging] + + extensions: [health_check, pprof, zpages] diff --git a/internal/otel_collector/cmd/otelcol/main.go b/internal/otel_collector/cmd/otelcol/main.go new file mode 100644 index 00000000000..f0a00053790 --- /dev/null +++ b/internal/otel_collector/cmd/otelcol/main.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Program otelcol is the OpenTelemetry Collector that collects stats +// and traces and exports to a configured backend. +package main + +import ( + "fmt" + "log" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/internal/version" + "go.opentelemetry.io/collector/service" + "go.opentelemetry.io/collector/service/defaultcomponents" +) + +func main() { + factories, err := defaultcomponents.Components() + if err != nil { + log.Fatalf("failed to build default components: %v", err) + } + + info := component.ApplicationStartInfo{ + ExeName: "otelcol", + LongName: "OpenTelemetry Collector", + Version: version.Version, + GitHash: version.GitHash, + } + + if err := run(service.Parameters{ApplicationStartInfo: info, Factories: factories}); err != nil { + log.Fatal(err) + } +} + +func runInteractive(params service.Parameters) error { + app, err := service.New(params) + if err != nil { + return fmt.Errorf("failed to construct the application: %w", err) + } + + err = app.Run() + if err != nil { + return fmt.Errorf("application run finished with error: %w", err) + } + + return nil +} diff --git a/internal/otel_collector/cmd/otelcol/main_others.go b/internal/otel_collector/cmd/otelcol/main_others.go new file mode 100644 index 00000000000..e1957c2fb71 --- /dev/null +++ b/internal/otel_collector/cmd/otelcol/main_others.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package main + +import "go.opentelemetry.io/collector/service" + +func run(params service.Parameters) error { + return runInteractive(params) +} diff --git a/internal/otel_collector/cmd/otelcol/main_windows.go b/internal/otel_collector/cmd/otelcol/main_windows.go new file mode 100644 index 00000000000..e30e866e95a --- /dev/null +++ b/internal/otel_collector/cmd/otelcol/main_windows.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package main + +import ( + "fmt" + + "golang.org/x/sys/windows/svc" + + "go.opentelemetry.io/collector/service" +) + +func run(params service.Parameters) error { + isInteractive, err := svc.IsAnInteractiveSession() + if err != nil { + return fmt.Errorf("failed to determine if we are running in an interactive session %w", err) + } + + if isInteractive { + return runInteractive(params) + } else { + return runService(params) + } +} + +func runService(params service.Parameters) error { + // do not need to supply service name when startup is invoked through Service Control Manager directly + if err := svc.Run("", service.NewWindowsService(params)); err != nil { + return fmt.Errorf("failed to start service %w", err) + } + + return nil +} diff --git a/internal/otel_collector/cmd/pdatagen/internal/base_fields.go b/internal/otel_collector/cmd/pdatagen/internal/base_fields.go new file mode 100644 index 00000000000..32b9d05bd52 --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/base_fields.go @@ -0,0 +1,343 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "os" + "strings" +) + +const accessorSliceTemplate = `// ${fieldName} returns the ${originFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) ${fieldName}() ${returnType} { + return new${returnType}(&(*ms.orig).${originFieldName}) +}` + +const accessorsSliceTestTemplate = `func Test${structName}_${fieldName}(t *testing.T) { + ms := New${structName}() + assert.EqualValues(t, New${returnType}(), ms.${fieldName}()) + fillTest${returnType}(ms.${fieldName}()) + testVal${fieldName} := generateTest${returnType}() + assert.EqualValues(t, testVal${fieldName}, ms.${fieldName}()) +}` + +const accessorsMessageValueTemplate = `// ${fieldName} returns the ${lowerFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) ${fieldName}() ${returnType} { + return new${returnType}(&(*ms.orig).${originFieldName}) +}` + +const accessorsMessageValueTestTemplate = `func Test${structName}_${fieldName}(t *testing.T) { + ms := New${structName}() + fillTest${returnType}(ms.${fieldName}()) + assert.EqualValues(t, generateTest${returnType}(), ms.${fieldName}()) +}` + +const accessorsPrimitiveTemplate = `// ${fieldName} returns the ${lowerFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) ${fieldName}() ${returnType} { + return (*ms.orig).${originFieldName} +} + +// Set${fieldName} replaces the ${lowerFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) Set${fieldName}(v ${returnType}) { + (*ms.orig).${originFieldName} = v +}` + +const accessorsPrimitiveTestTemplate = `func Test${structName}_${fieldName}(t *testing.T) { + ms := New${structName}() + assert.EqualValues(t, ${defaultVal}, ms.${fieldName}()) + testVal${fieldName} := ${testValue} + ms.Set${fieldName}(testVal${fieldName}) + assert.EqualValues(t, testVal${fieldName}, ms.${fieldName}()) +}` + +const accessorsPrimitiveTypedTemplate = `// ${fieldName} returns the ${lowerFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) ${fieldName}() ${returnType} { + return ${returnType}((*ms.orig).${originFieldName}) +} + +// Set${fieldName} replaces the ${lowerFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) Set${fieldName}(v ${returnType}) { + (*ms.orig).${originFieldName} = ${rawType}(v) +}` + +const accessorsPrimitiveWithoutSetterTypedTemplate = `// ${fieldName} returns the ${lowerFieldName} associated with this ${structName}. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ${structName}) ${fieldName}() ${returnType} { + return ${returnType}((*ms.orig).${originFieldName}) +}` + +type baseField interface { + generateAccessors(ms baseStruct, sb *strings.Builder) + + generateAccessorsTest(ms baseStruct, sb *strings.Builder) + + generateSetWithTestValue(sb *strings.Builder) + + generateCopyToValue(sb *strings.Builder) +} + +type sliceField struct { + fieldName string + originFieldName string + returnSlice baseSlice +} + +func (sf *sliceField) generateAccessors(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorSliceTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "fieldName": + return sf.fieldName + case "returnType": + return sf.returnSlice.getName() + case "originFieldName": + return sf.originFieldName + default: + panic(name) + } + })) +} + +func (sf *sliceField) generateAccessorsTest(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorsSliceTestTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "fieldName": + return sf.fieldName + case "returnType": + return sf.returnSlice.getName() + default: + panic(name) + } + })) +} + +func (sf *sliceField) generateSetWithTestValue(sb *strings.Builder) { + sb.WriteString("\tfillTest" + sf.returnSlice.getName() + "(tv." + sf.fieldName + "())") +} + +func (sf *sliceField) generateCopyToValue(sb *strings.Builder) { + sb.WriteString("\tms." + sf.fieldName + "().CopyTo(dest." + sf.fieldName + "())") +} + +var _ baseField = (*sliceField)(nil) + +type messageValueField struct { + fieldName string + originFieldName string + returnMessage *messageValueStruct +} + +func (mf *messageValueField) generateAccessors(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorsMessageValueTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "fieldName": + return mf.fieldName + case "lowerFieldName": + return strings.ToLower(mf.fieldName) + case "returnType": + return mf.returnMessage.structName + case "structOriginFullName": + return mf.returnMessage.originFullName + case "originFieldName": + return mf.originFieldName + default: + panic(name) + } + })) +} + +func (mf *messageValueField) generateAccessorsTest(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorsMessageValueTestTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "fieldName": + return mf.fieldName + case "returnType": + return mf.returnMessage.structName + default: + panic(name) + } + })) +} + +func (mf *messageValueField) generateSetWithTestValue(sb *strings.Builder) { + sb.WriteString("\tfillTest" + mf.returnMessage.structName + "(tv." + mf.fieldName + "())") +} + +func (mf *messageValueField) generateCopyToValue(sb *strings.Builder) { + sb.WriteString("\tms." + mf.fieldName + "().CopyTo(dest." + mf.fieldName + "())") +} + +var _ baseField = (*messageValueField)(nil) + +type primitiveField struct { + fieldName string + originFieldName string + returnType string + defaultVal string + testVal string +} + +func (pf *primitiveField) generateAccessors(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorsPrimitiveTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "fieldName": + return pf.fieldName + case "lowerFieldName": + return strings.ToLower(pf.fieldName) + case "returnType": + return pf.returnType + case "originFieldName": + return pf.originFieldName + default: + panic(name) + } + })) +} + +func (pf *primitiveField) generateAccessorsTest(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorsPrimitiveTestTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "defaultVal": + return pf.defaultVal + case "fieldName": + return pf.fieldName + case "testValue": + return pf.testVal + default: + panic(name) + } + })) +} + +func (pf *primitiveField) generateSetWithTestValue(sb *strings.Builder) { + sb.WriteString("\ttv.Set" + pf.fieldName + "(" + pf.testVal + ")") +} + +func (pf *primitiveField) generateCopyToValue(sb *strings.Builder) { + sb.WriteString("\tdest.Set" + pf.fieldName + "(ms." + pf.fieldName + "())") +} + +var _ baseField = (*primitiveField)(nil) + +// Types that has defined a custom type (e.g. "type TimestampUnixNano uint64") +type primitiveTypedField struct { + fieldName string + originFieldName string + returnType string + defaultVal string + testVal string + rawType string + manualSetter bool +} + +func (ptf *primitiveTypedField) generateAccessors(ms baseStruct, sb *strings.Builder) { + template := accessorsPrimitiveTypedTemplate + if ptf.manualSetter { + // Generate code without setter. Setter will be manually coded. + template = accessorsPrimitiveWithoutSetterTypedTemplate + } + + sb.WriteString(os.Expand(template, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "fieldName": + return ptf.fieldName + case "lowerFieldName": + return strings.ToLower(ptf.fieldName) + case "returnType": + return ptf.returnType + case "rawType": + return ptf.rawType + case "originFieldName": + return ptf.originFieldName + default: + panic(name) + } + })) +} + +func (ptf *primitiveTypedField) generateAccessorsTest(ms baseStruct, sb *strings.Builder) { + sb.WriteString(os.Expand(accessorsPrimitiveTestTemplate, func(name string) string { + switch name { + case "structName": + return ms.getName() + case "defaultVal": + return ptf.defaultVal + case "fieldName": + return ptf.fieldName + case "testValue": + return ptf.testVal + default: + panic(name) + } + })) +} + +func (ptf *primitiveTypedField) generateSetWithTestValue(sb *strings.Builder) { + sb.WriteString("\ttv.Set" + ptf.fieldName + "(" + ptf.testVal + ")") +} + +func (ptf *primitiveTypedField) generateCopyToValue(sb *strings.Builder) { + sb.WriteString("\tdest.Set" + ptf.fieldName + "(ms." + ptf.fieldName + "())") +} + +var _ baseField = (*primitiveTypedField)(nil) + +// oneofField is used in case where the proto defines an "oneof". +type oneofField struct { + copyFuncName string + originFieldName string + testVal string + fillTestName string +} + +func (one oneofField) generateAccessors(baseStruct, *strings.Builder) {} + +func (one oneofField) generateAccessorsTest(baseStruct, *strings.Builder) {} + +func (one oneofField) generateSetWithTestValue(sb *strings.Builder) { + sb.WriteString("\t(*tv.orig)." + one.originFieldName + " = " + one.testVal + "\n") + sb.WriteString("\tfillTest" + one.fillTestName + "(tv." + one.fillTestName + "())") +} + +func (one oneofField) generateCopyToValue(sb *strings.Builder) { + sb.WriteString("\t" + one.copyFuncName + "(ms.orig, dest.orig)") +} + +var _ baseField = (*oneofField)(nil) diff --git a/internal/otel_collector/cmd/pdatagen/internal/base_slices.go b/internal/otel_collector/cmd/pdatagen/internal/base_slices.go new file mode 100644 index 00000000000..5a0e8bdf144 --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/base_slices.go @@ -0,0 +1,609 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "os" + "strings" +) + +const slicePtrTemplate = `// ${structName} logically represents a slice of ${elementName}. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use New${structName} function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ${structName} struct { + // orig points to the slice ${originName} field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*${originName} +} + +func new${structName}(orig *[]*${originName}) ${structName} { + return ${structName}{orig} +} + +// New${structName} creates a ${structName} with 0 elements. +// Can use "Resize" to initialize with a given length. +func New${structName}() ${structName} { + orig := []*${originName}(nil) + return ${structName}{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "New${structName}()". +func (es ${structName}) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es ${structName}) At(ix int) ${elementName} { + return new${elementName}((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es ${structName}) MoveAndAppendTo(dest ${structName}) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es ${structName}) CopyTo(dest ${structName}) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + new${elementName}((*es.orig)[i]).CopyTo(new${elementName}((*dest.orig)[i])) + } + return + } + origs := make([]${originName}, srcLen) + wrappers := make([]*${originName}, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + new${elementName}((*es.orig)[i]).CopyTo(new${elementName}(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new ${structName} can be initialized: +// es := New${structName}() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es ${structName}) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*${originName}, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]${originName}, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the ${structName} by one and set the +// given ${elementName} at that new position. The original ${elementName} +// could still be referenced so do not reuse it after passing it to this +// method. +func (es ${structName}) Append(e ${elementName}) { + *es.orig = append(*es.orig, e.orig) +}` + +const slicePtrTestTemplate = `func Test${structName}(t *testing.T) { + es := New${structName}() + assert.EqualValues(t, 0, es.Len()) + es = new${structName}(&[]*${originName}{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := New${elementName}() + testVal := generateTest${elementName}() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTest${elementName}(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func Test${structName}_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTest${structName}() + dest := New${structName}() + src := generateTest${structName}() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTest${structName}().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func Test${structName}_CopyTo(t *testing.T) { + dest := New${structName}() + // Test CopyTo to empty + New${structName}().CopyTo(dest) + assert.EqualValues(t, New${structName}(), dest) + + // Test CopyTo larger slice + generateTest${structName}().CopyTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) + + // Test CopyTo same size slice + generateTest${structName}().CopyTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) +} + +func Test${structName}_Resize(t *testing.T) { + es := generateTest${structName}() + emptyVal := New${elementName}() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*${originName}]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*${originName}]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*${originName}]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*${originName}]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func Test${structName}_Append(t *testing.T) { + es := generateTest${structName}() + + emptyVal := New${elementName}() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := New${elementName}() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +}` + +const slicePtrGenerateTest = `func generateTest${structName}() ${structName} { + tv := New${structName}() + fillTest${structName}(tv) + return tv +} + +func fillTest${structName}(tv ${structName}) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTest${elementName}(tv.At(i)) + } +}` + +const sliceValueTemplate = `// ${structName} logically represents a slice of ${elementName}. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use New${structName} function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ${structName} struct { + // orig points to the slice ${originName} field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]${originName} +} + +func new${structName}(orig *[]${originName}) ${structName} { + return ${structName}{orig} +} + +// New${structName} creates a ${structName} with 0 elements. +// Can use "Resize" to initialize with a given length. +func New${structName}() ${structName} { + orig := []${originName}(nil) + return ${structName}{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "New${structName}()". +func (es ${structName}) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es ${structName}) At(ix int) ${elementName} { + return new${elementName}(&(*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es ${structName}) MoveAndAppendTo(dest ${structName}) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es ${structName}) CopyTo(dest ${structName}) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + } else { + (*dest.orig) = make([]${originName}, srcLen) + } + + for i := range *es.orig { + new${elementName}(&(*es.orig)[i]).CopyTo(new${elementName}(&(*dest.orig)[i])) + } +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new ${structName} can be initialized: +// es := New${structName}() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es ${structName}) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]${originName}, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + empty := otlpcommon.AnyValue{} + for i := oldLen; i < newLen; i++ { + *es.orig = append(*es.orig, empty) + } +} + +// Append will increase the length of the ${structName} by one and set the +// given ${elementName} at that new position. The original ${elementName} +// could still be referenced so do not reuse it after passing it to this +// method. +func (es ${structName}) Append(e ${elementName}) { + *es.orig = append(*es.orig, *e.orig) +}` + +const sliceValueTestTemplate = `func Test${structName}(t *testing.T) { + es := New${structName}() + assert.EqualValues(t, 0, es.Len()) + es = new${structName}(&[]${originName}{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := New${elementName}() + testVal := generateTest${elementName}() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTest${elementName}(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func Test${structName}_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTest${structName}() + dest := New${structName}() + src := generateTest${structName}() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTest${structName}().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func Test${structName}_CopyTo(t *testing.T) { + dest := New${structName}() + // Test CopyTo to empty + New${structName}().CopyTo(dest) + assert.EqualValues(t, New${structName}(), dest) + + // Test CopyTo larger slice + generateTest${structName}().CopyTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) + + // Test CopyTo same size slice + generateTest${structName}().CopyTo(dest) + assert.EqualValues(t, generateTest${structName}(), dest) +} + +func Test${structName}_Resize(t *testing.T) { + es := generateTest${structName}() + emptyVal := New${elementName}() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*${originName}]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*${originName}]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*${originName}]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*${originName}]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func Test${structName}_Append(t *testing.T) { + es := generateTest${structName}() + + emptyVal := New${elementName}() + es.Append(emptyVal) + assert.EqualValues(t, *(es.At(7)).orig, *emptyVal.orig) + + emptyVal2 := New${elementName}() + es.Append(emptyVal2) + assert.EqualValues(t, *(es.At(8)).orig, *emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +}` + +const sliceValueGenerateTest = `func generateTest${structName}() ${structName} { + tv := New${structName}() + fillTest${structName}(tv) + return tv +} + +func fillTest${structName}(tv ${structName}) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTest${elementName}(tv.At(i)) + } +}` + +type baseSlice interface { + getName() string +} + +// Will generate code only for a slice of pointer fields. +type sliceOfPtrs struct { + structName string + element *messageValueStruct +} + +func (ss *sliceOfPtrs) getName() string { + return ss.structName +} + +func (ss *sliceOfPtrs) generateStruct(sb *strings.Builder) { + sb.WriteString(os.Expand(slicePtrTemplate, func(name string) string { + switch name { + case "structName": + return ss.structName + case "elementName": + return ss.element.structName + case "originName": + return ss.element.originFullName + default: + panic(name) + } + })) +} + +func (ss *sliceOfPtrs) generateTests(sb *strings.Builder) { + sb.WriteString(os.Expand(slicePtrTestTemplate, func(name string) string { + switch name { + case "structName": + return ss.structName + case "elementName": + return ss.element.structName + case "originName": + return ss.element.originFullName + default: + panic(name) + } + })) +} + +func (ss *sliceOfPtrs) generateTestValueHelpers(sb *strings.Builder) { + sb.WriteString(os.Expand(slicePtrGenerateTest, func(name string) string { + switch name { + case "structName": + return ss.structName + case "elementName": + return ss.element.structName + default: + panic(name) + } + })) +} + +var _ baseStruct = (*sliceOfPtrs)(nil) + +// Will generate code only for a slice of value fields. +type sliceOfValues struct { + structName string + element *messageValueStruct +} + +func (ss *sliceOfValues) getName() string { + return ss.structName +} + +func (ss *sliceOfValues) generateStruct(sb *strings.Builder) { + sb.WriteString(os.Expand(sliceValueTemplate, func(name string) string { + switch name { + case "structName": + return ss.structName + case "elementName": + return ss.element.structName + case "originName": + return ss.element.originFullName + default: + panic(name) + } + })) +} + +func (ss *sliceOfValues) generateTests(sb *strings.Builder) { + sb.WriteString(os.Expand(sliceValueTestTemplate, func(name string) string { + switch name { + case "structName": + return ss.structName + case "elementName": + return ss.element.structName + case "originName": + return ss.element.originFullName + default: + panic(name) + } + })) +} + +func (ss *sliceOfValues) generateTestValueHelpers(sb *strings.Builder) { + sb.WriteString(os.Expand(sliceValueGenerateTest, func(name string) string { + switch name { + case "structName": + return ss.structName + case "elementName": + return ss.element.structName + default: + panic(name) + } + })) +} + +var _ baseStruct = (*sliceOfValues)(nil) diff --git a/internal/otel_collector/cmd/pdatagen/internal/base_structs.go b/internal/otel_collector/cmd/pdatagen/internal/base_structs.go new file mode 100644 index 00000000000..c2686679577 --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/base_structs.go @@ -0,0 +1,173 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "os" + "strings" +) + +const messageValueTemplate = `${description} +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use New${structName} function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ${structName} struct { + orig *${originName} +} + +func new${structName}(orig *${originName}) ${structName} { + return ${structName}{orig: orig} +} + +// New${structName} creates a new empty ${structName}. +// +// This must be used only in testing code since no "Set" method available. +func New${structName}() ${structName} { + return new${structName}(&${originName}{}) +}` + +const messageValueCopyToHeaderTemplate = `// CopyTo copies all properties from the current struct to the dest. +func (ms ${structName}) CopyTo(dest ${structName}) {` + +const messageValueCopyToFooterTemplate = `}` + +const messageValueTestTemplate = ` +func Test${structName}_CopyTo(t *testing.T) { + ms := New${structName}() + generateTest${structName}().CopyTo(ms) + assert.EqualValues(t, generateTest${structName}(), ms) +}` + +const messageValueGenerateTestTemplate = `func generateTest${structName}() ${structName} { + tv := New${structName}() + fillTest${structName}(tv) + return tv +}` + +const messageValueFillTestHeaderTemplate = `func fillTest${structName}(tv ${structName}) {` +const messageValueFillTestFooterTemplate = `}` + +const newLine = "\n" + +type baseStruct interface { + getName() string + + generateStruct(sb *strings.Builder) + + generateTests(sb *strings.Builder) + + generateTestValueHelpers(sb *strings.Builder) +} + +type messageValueStruct struct { + structName string + description string + originFullName string + fields []baseField +} + +func (ms *messageValueStruct) getName() string { + return ms.structName +} + +func (ms *messageValueStruct) generateStruct(sb *strings.Builder) { + sb.WriteString(os.Expand(messageValueTemplate, func(name string) string { + switch name { + case "structName": + return ms.structName + case "originName": + return ms.originFullName + case "description": + return ms.description + default: + panic(name) + } + })) + // Write accessors for the struct + for _, f := range ms.fields { + sb.WriteString(newLine + newLine) + f.generateAccessors(ms, sb) + } + sb.WriteString(newLine + newLine) + sb.WriteString(os.Expand(messageValueCopyToHeaderTemplate, func(name string) string { + switch name { + case "structName": + return ms.structName + default: + panic(name) + } + })) + // Write accessors CopyTo for the struct + for _, f := range ms.fields { + sb.WriteString(newLine) + f.generateCopyToValue(sb) + } + sb.WriteString(newLine) + sb.WriteString(os.Expand(messageValueCopyToFooterTemplate, func(name string) string { + panic(name) + })) +} + +func (ms *messageValueStruct) generateTests(sb *strings.Builder) { + sb.WriteString(os.Expand(messageValueTestTemplate, func(name string) string { + switch name { + case "structName": + return ms.structName + default: + panic(name) + } + })) + // Write accessors tests for the struct + for _, f := range ms.fields { + sb.WriteString(newLine + newLine) + f.generateAccessorsTest(ms, sb) + } +} + +func (ms *messageValueStruct) generateTestValueHelpers(sb *strings.Builder) { + sb.WriteString(os.Expand(messageValueGenerateTestTemplate, func(name string) string { + switch name { + case "structName": + return ms.structName + case "originName": + return ms.originFullName + default: + panic(name) + } + })) + sb.WriteString(newLine + newLine) + sb.WriteString(os.Expand(messageValueFillTestHeaderTemplate, func(name string) string { + switch name { + case "structName": + return ms.structName + default: + panic(name) + } + })) + // Write accessors test value for the struct + for _, f := range ms.fields { + sb.WriteString(newLine) + f.generateSetWithTestValue(sb) + } + sb.WriteString(newLine) + sb.WriteString(os.Expand(messageValueFillTestFooterTemplate, func(name string) string { + panic(name) + })) +} + +var _ baseStruct = (*messageValueStruct)(nil) diff --git a/internal/otel_collector/cmd/pdatagen/internal/common_structs.go b/internal/otel_collector/cmd/pdatagen/internal/common_structs.go new file mode 100644 index 00000000000..1c3868acd35 --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/common_structs.go @@ -0,0 +1,124 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +var commonFile = &File{ + Name: "common", + imports: []string{ + `otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1"`, + }, + testImports: []string{ + `"testing"`, + ``, + `"github.com/stretchr/testify/assert"`, + ``, + `otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1"`, + }, + structs: []baseStruct{ + instrumentationLibrary, + anyValueArray, + }, +} + +var instrumentationLibrary = &messageValueStruct{ + structName: "InstrumentationLibrary", + description: "// InstrumentationLibrary is a message representing the instrumentation library information.", + originFullName: "otlpcommon.InstrumentationLibrary", + fields: []baseField{ + nameField, + &primitiveField{ + fieldName: "Version", + originFieldName: "Version", + returnType: "string", + defaultVal: `""`, + testVal: `"test_version"`, + }, + }, +} + +// This will not be generated by this class. +// Defined here just to be available as returned message for the fields. +var stringMap = &sliceOfPtrs{ + structName: "StringMap", + element: stringKeyValue, +} + +var stringKeyValue = &messageValueStruct{} + +// This will not be generated by this class. +// Defined here just to be available as returned message for the fields. +var attributeMap = &sliceOfPtrs{ + structName: "AttributeMap", + element: attributeKeyValue, +} + +var attributeKeyValue = &messageValueStruct{} + +var instrumentationLibraryField = &messageValueField{ + fieldName: "InstrumentationLibrary", + originFieldName: "InstrumentationLibrary", + returnMessage: instrumentationLibrary, +} + +var startTimeField = &primitiveTypedField{ + fieldName: "StartTime", + originFieldName: "StartTimeUnixNano", + returnType: "TimestampUnixNano", + rawType: "uint64", + defaultVal: "TimestampUnixNano(0)", + testVal: "TimestampUnixNano(1234567890)", +} + +var timeField = &primitiveTypedField{ + fieldName: "Timestamp", + originFieldName: "TimeUnixNano", + returnType: "TimestampUnixNano", + rawType: "uint64", + defaultVal: "TimestampUnixNano(0)", + testVal: "TimestampUnixNano(1234567890)", +} + +var endTimeField = &primitiveTypedField{ + fieldName: "EndTime", + originFieldName: "EndTimeUnixNano", + returnType: "TimestampUnixNano", + rawType: "uint64", + defaultVal: "TimestampUnixNano(0)", + testVal: "TimestampUnixNano(1234567890)", +} + +var attributes = &sliceField{ + fieldName: "Attributes", + originFieldName: "Attributes", + returnSlice: attributeMap, +} + +var nameField = &primitiveField{ + fieldName: "Name", + originFieldName: "Name", + returnType: "string", + defaultVal: `""`, + testVal: `"test_name"`, +} + +var anyValue = &messageValueStruct{ + structName: "AttributeValue", + originFullName: "otlpcommon.AnyValue", +} + +var anyValueArray = &sliceOfValues{ + structName: "AnyValueArray", + element: anyValue, +} diff --git a/internal/otel_collector/cmd/pdatagen/internal/files.go b/internal/otel_collector/cmd/pdatagen/internal/files.go new file mode 100644 index 00000000000..4971e1187ef --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/files.go @@ -0,0 +1,106 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import "strings" + +const header = `// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata` + +// AllFiles is a list of all files that needs to be generated. +var AllFiles = []*File{ + commonFile, + metricsFile, + resourceFile, + traceFile, + logFile, +} + +// File represents the struct for one generated file. +type File struct { + Name string + imports []string + testImports []string + // Can be any of sliceOfPtrs, sliceOfValues, messageValueStruct, or messagePtrStruct + structs []baseStruct +} + +// GenerateFile generates the file string. +func (f *File) GenerateFile() string { + var sb strings.Builder + + // Write headers + sb.WriteString(header) + sb.WriteString(newLine + newLine) + // Add imports + sb.WriteString("import (" + newLine) + for _, i := range f.imports { + sb.WriteString("\t" + i + newLine) + } + sb.WriteString(")") + // Write all structs + for _, s := range f.structs { + sb.WriteString(newLine + newLine) + s.generateStruct(&sb) + } + sb.WriteString(newLine) + return sb.String() +} + +func (f *File) GenerateTestFile() string { + var sb strings.Builder + + // Write headers + sb.WriteString(header) + sb.WriteString(newLine + newLine) + // Add imports + sb.WriteString("import (" + newLine) + for _, imp := range f.testImports { + if imp != "" { + sb.WriteString("\t" + imp + newLine) + } else { + sb.WriteString(newLine) + } + } + sb.WriteString(")") + // Write all tests + for _, s := range f.structs { + sb.WriteString(newLine + newLine) + s.generateTests(&sb) + } + // Write all tests generate value + for _, s := range f.structs { + sb.WriteString(newLine + newLine) + s.generateTestValueHelpers(&sb) + } + sb.WriteString(newLine) + return sb.String() +} diff --git a/internal/otel_collector/cmd/pdatagen/internal/log_structs.go b/internal/otel_collector/cmd/pdatagen/internal/log_structs.go new file mode 100644 index 00000000000..35b6a7522cb --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/log_structs.go @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +var logFile = &File{ + Name: "log", + imports: []string{ + `otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1"`, + `otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1"`, + }, + testImports: []string{ + `"testing"`, + ``, + `"github.com/stretchr/testify/assert"`, + ``, + `otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1"`, + }, + structs: []baseStruct{ + resourceLogsSlice, + resourceLogs, + instrumentationLibraryLogsSlice, + instrumentationLibraryLogs, + logSlice, + logRecord, + }, +} + +var resourceLogsSlice = &sliceOfPtrs{ + structName: "ResourceLogsSlice", + element: resourceLogs, +} + +var resourceLogs = &messageValueStruct{ + structName: "ResourceLogs", + description: "// ResourceLogs is a collection of logs from a Resource.", + originFullName: "otlplogs.ResourceLogs", + fields: []baseField{ + resourceField, + &sliceField{ + fieldName: "InstrumentationLibraryLogs", + originFieldName: "InstrumentationLibraryLogs", + returnSlice: instrumentationLibraryLogsSlice, + }, + }, +} + +var instrumentationLibraryLogsSlice = &sliceOfPtrs{ + structName: "InstrumentationLibraryLogsSlice", + element: instrumentationLibraryLogs, +} + +var instrumentationLibraryLogs = &messageValueStruct{ + structName: "InstrumentationLibraryLogs", + description: "// InstrumentationLibraryLogs is a collection of logs from a LibraryInstrumentation.", + originFullName: "otlplogs.InstrumentationLibraryLogs", + fields: []baseField{ + instrumentationLibraryField, + &sliceField{ + fieldName: "Logs", + originFieldName: "Logs", + returnSlice: logSlice, + }, + }, +} + +var logSlice = &sliceOfPtrs{ + structName: "LogSlice", + element: logRecord, +} + +var logRecord = &messageValueStruct{ + structName: "LogRecord", + description: "// LogRecord are experimental implementation of OpenTelemetry Log Data Model.\n", + originFullName: "otlplogs.LogRecord", + fields: []baseField{ + &primitiveTypedField{ + fieldName: "Timestamp", + originFieldName: "TimeUnixNano", + returnType: "TimestampUnixNano", + rawType: "uint64", + defaultVal: "TimestampUnixNano(0)", + testVal: "TimestampUnixNano(1234567890)", + }, + traceIDField, + spanIDField, + &primitiveTypedField{ + fieldName: "Flags", + originFieldName: "Flags", + returnType: "uint32", + rawType: "uint32", + defaultVal: `uint32(0)`, + testVal: `uint32(0x01)`, + }, + &primitiveField{ + fieldName: "SeverityText", + originFieldName: "SeverityText", + returnType: "string", + defaultVal: `""`, + testVal: `"INFO"`, + }, + &primitiveTypedField{ + fieldName: "SeverityNumber", + originFieldName: "SeverityNumber", + returnType: "SeverityNumber", + rawType: "otlplogs.SeverityNumber", + defaultVal: `SeverityNumberUNDEFINED`, + testVal: `SeverityNumberINFO`, + }, + &primitiveField{ + fieldName: "Name", + originFieldName: "Name", + returnType: "string", + defaultVal: `""`, + testVal: `"test_name"`, + }, + bodyField, + attributes, + droppedAttributesCount, + }, +} + +var bodyField = &messageValueField{ + fieldName: "Body", + originFieldName: "Body", + returnMessage: anyValue, +} diff --git a/internal/otel_collector/cmd/pdatagen/internal/metrics_structs.go b/internal/otel_collector/cmd/pdatagen/internal/metrics_structs.go new file mode 100644 index 00000000000..d38a3182add --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/metrics_structs.go @@ -0,0 +1,493 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +var metricsFile = &File{ + Name: "metrics", + imports: []string{ + `otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1"`, + }, + testImports: []string{ + `"testing"`, + ``, + `"github.com/stretchr/testify/assert"`, + ``, + `otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1"`, + }, + structs: []baseStruct{ + resourceMetricsSlice, + resourceMetrics, + instrumentationLibraryMetricsSlice, + instrumentationLibraryMetrics, + metricSlice, + metric, + intGauge, + doubleGauge, + intSum, + doubleSum, + intHistogram, + doubleHistogram, + doubleSummary, + intDataPointSlice, + intDataPoint, + doubleDataPointSlice, + doubleDataPoint, + intHistogramDataPointSlice, + intHistogramDataPoint, + doubleHistogramDataPointSlice, + doubleHistogramDataPoint, + doubleSummaryDataPointSlice, + doubleSummaryDataPoint, + quantileValuesSlice, + quantileValues, + intExemplarSlice, + intExemplar, + doubleExemplarSlice, + doubleExemplar, + }, +} + +var resourceMetricsSlice = &sliceOfPtrs{ + structName: "ResourceMetricsSlice", + element: resourceMetrics, +} + +var resourceMetrics = &messageValueStruct{ + structName: "ResourceMetrics", + description: "// InstrumentationLibraryMetrics is a collection of metrics from a LibraryInstrumentation.", + originFullName: "otlpmetrics.ResourceMetrics", + fields: []baseField{ + resourceField, + &sliceField{ + fieldName: "InstrumentationLibraryMetrics", + originFieldName: "InstrumentationLibraryMetrics", + returnSlice: instrumentationLibraryMetricsSlice, + }, + }, +} + +var instrumentationLibraryMetricsSlice = &sliceOfPtrs{ + structName: "InstrumentationLibraryMetricsSlice", + element: instrumentationLibraryMetrics, +} + +var instrumentationLibraryMetrics = &messageValueStruct{ + structName: "InstrumentationLibraryMetrics", + description: "// InstrumentationLibraryMetrics is a collection of metrics from a LibraryInstrumentation.", + originFullName: "otlpmetrics.InstrumentationLibraryMetrics", + fields: []baseField{ + instrumentationLibraryField, + &sliceField{ + fieldName: "Metrics", + originFieldName: "Metrics", + returnSlice: metricSlice, + }, + }, +} + +var metricSlice = &sliceOfPtrs{ + structName: "MetricSlice", + element: metric, +} + +var metric = &messageValueStruct{ + structName: "Metric", + description: "// Metric represents one metric as a collection of datapoints.\n" + + "// See Metric definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/metrics/v1/metrics.proto", + originFullName: "otlpmetrics.Metric", + fields: []baseField{ + nameField, + &primitiveField{ + fieldName: "Description", + originFieldName: "Description", + returnType: "string", + defaultVal: `""`, + testVal: `"test_description"`, + }, + &primitiveField{ + fieldName: "Unit", + originFieldName: "Unit", + returnType: "string", + defaultVal: `""`, + testVal: `"1"`, + }, + oneofDataField, + }, +} + +var intGauge = &messageValueStruct{ + structName: "IntGauge", + description: "// IntGauge represents the type of a int scalar metric that always exports the \"current value\" for every data point.", + originFullName: "otlpmetrics.IntGauge", + fields: []baseField{ + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: intDataPointSlice, + }, + }, +} + +var doubleGauge = &messageValueStruct{ + structName: "DoubleGauge", + description: "// DoubleGauge represents the type of a double scalar metric that always exports the \"current value\" for every data point.", + originFullName: "otlpmetrics.DoubleGauge", + fields: []baseField{ + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: doubleDataPointSlice, + }, + }, +} + +var intSum = &messageValueStruct{ + structName: "IntSum", + description: "// IntSum represents the type of a numeric int scalar metric that is calculated as a sum of all reported measurements over a time interval.", + originFullName: "otlpmetrics.IntSum", + fields: []baseField{ + aggregationTemporalityField, + isMonotonicField, + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: intDataPointSlice, + }, + }, +} + +var doubleSum = &messageValueStruct{ + structName: "DoubleSum", + description: "// DoubleSum represents the type of a numeric double scalar metric that is calculated as a sum of all reported measurements over a time interval.", + originFullName: "otlpmetrics.DoubleSum", + fields: []baseField{ + aggregationTemporalityField, + isMonotonicField, + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: doubleDataPointSlice, + }, + }, +} + +var intHistogram = &messageValueStruct{ + structName: "IntHistogram", + description: "// IntHistogram represents the type of a metric that is calculated by aggregating as a Histogram of all reported double measurements over a time interval.", + originFullName: "otlpmetrics.IntHistogram", + fields: []baseField{ + aggregationTemporalityField, + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: intHistogramDataPointSlice, + }, + }, +} + +var doubleHistogram = &messageValueStruct{ + structName: "DoubleHistogram", + description: "// DoubleHistogram represents the type of a metric that is calculated by aggregating as a Histogram of all reported double measurements over a time interval.", + originFullName: "otlpmetrics.DoubleHistogram", + fields: []baseField{ + aggregationTemporalityField, + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: doubleHistogramDataPointSlice, + }, + }, +} + +var doubleSummary = &messageValueStruct{ + structName: "DoubleSummary", + description: "// DoubleSummary represents the type of a metric that is calculated by aggregating as a Summary of all reported double measurements over a time interval.", + originFullName: "otlpmetrics.DoubleSummary", + fields: []baseField{ + &sliceField{ + fieldName: "DataPoints", + originFieldName: "DataPoints", + returnSlice: doubleSummaryDataPointSlice, + }, + }, +} + +var intDataPointSlice = &sliceOfPtrs{ + structName: "IntDataPointSlice", + element: intDataPoint, +} + +var intDataPoint = &messageValueStruct{ + structName: "IntDataPoint", + description: "// IntDataPoint is a single data point in a timeseries that describes the time-varying values of a scalar int metric.", + originFullName: "otlpmetrics.IntDataPoint", + fields: []baseField{ + labelsField, + startTimeField, + timeField, + valueInt64Field, + intExemplarsField, + }, +} + +var doubleDataPointSlice = &sliceOfPtrs{ + structName: "DoubleDataPointSlice", + element: doubleDataPoint, +} + +var doubleDataPoint = &messageValueStruct{ + structName: "DoubleDataPoint", + description: "// DoubleDataPoint is a single data point in a timeseries that describes the time-varying value of a double metric.", + originFullName: "otlpmetrics.DoubleDataPoint", + fields: []baseField{ + labelsField, + startTimeField, + timeField, + valueFloat64Field, + doubleExemplarsField, + }, +} + +var intHistogramDataPointSlice = &sliceOfPtrs{ + structName: "IntHistogramDataPointSlice", + element: intHistogramDataPoint, +} + +var intHistogramDataPoint = &messageValueStruct{ + structName: "IntHistogramDataPoint", + description: "// IntHistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of int values.", + originFullName: "otlpmetrics.IntHistogramDataPoint", + fields: []baseField{ + labelsField, + startTimeField, + timeField, + countField, + intSumField, + bucketCountsField, + explicitBoundsField, + intExemplarsField, + }, +} + +var doubleHistogramDataPointSlice = &sliceOfPtrs{ + structName: "DoubleHistogramDataPointSlice", + element: doubleHistogramDataPoint, +} + +var doubleHistogramDataPoint = &messageValueStruct{ + structName: "DoubleHistogramDataPoint", + description: "// DoubleHistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of double values.", + originFullName: "otlpmetrics.DoubleHistogramDataPoint", + fields: []baseField{ + labelsField, + startTimeField, + timeField, + countField, + doubleSumField, + bucketCountsField, + explicitBoundsField, + doubleExemplarsField, + }, +} + +var doubleSummaryDataPointSlice = &sliceOfPtrs{ + structName: "DoubleSummaryDataPointSlice", + element: doubleSummaryDataPoint, +} + +var doubleSummaryDataPoint = &messageValueStruct{ + structName: "DoubleSummaryDataPoint", + description: "// DoubleSummaryDataPoint is a single data point in a timeseries that describes the time-varying values of a Summary of double values.", + originFullName: "otlpmetrics.DoubleSummaryDataPoint", + fields: []baseField{ + labelsField, + startTimeField, + timeField, + countField, + doubleSumField, + &sliceField{ + fieldName: "QuantileValues", + originFieldName: "QuantileValues", + returnSlice: quantileValuesSlice, + }, + }, +} + +var quantileValuesSlice = &sliceOfPtrs{ + structName: "ValueAtQuantileSlice", + element: quantileValues, +} + +var quantileValues = &messageValueStruct{ + structName: "ValueAtQuantile", + description: "// ValueAtQuantile is a quantile value within a Summary data point", + originFullName: "otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile", + fields: []baseField{ + quantileField, + valueFloat64Field, + }, +} + +var intExemplarSlice = &sliceOfPtrs{ + structName: "IntExemplarSlice", + element: intExemplar, +} + +var intExemplar = &messageValueStruct{ + structName: "IntExemplar", + description: "// IntExemplar is a sample input int measurement.\n//\n" + + "// Exemplars also hold information about the environment when the measurement was recorded,\n" + + "// for example the span and trace ID of the active span when the exemplar was recorded.", + + originFullName: "otlpmetrics.IntExemplar", + fields: []baseField{ + timeField, + valueInt64Field, + &sliceField{ + fieldName: "FilteredLabels", + originFieldName: "FilteredLabels", + returnSlice: stringMap, + }, + }, +} + +var doubleExemplarSlice = &sliceOfPtrs{ + structName: "DoubleExemplarSlice", + element: doubleExemplar, +} + +var doubleExemplar = &messageValueStruct{ + structName: "DoubleExemplar", + description: "// DoubleExemplar is a sample input double measurement.\n//\n" + + "// Exemplars also hold information about the environment when the measurement was recorded,\n" + + "// for example the span and trace ID of the active span when the exemplar was recorded.", + + originFullName: "otlpmetrics.DoubleExemplar", + fields: []baseField{ + timeField, + valueFloat64Field, + &sliceField{ + fieldName: "FilteredLabels", + originFieldName: "FilteredLabels", + returnSlice: stringMap, + }, + }, +} + +var labelsField = &sliceField{ + fieldName: "LabelsMap", + originFieldName: "Labels", + returnSlice: stringMap, +} + +var intExemplarsField = &sliceField{ + fieldName: "Exemplars", + originFieldName: "Exemplars", + returnSlice: intExemplarSlice, +} + +var doubleExemplarsField = &sliceField{ + fieldName: "Exemplars", + originFieldName: "Exemplars", + returnSlice: doubleExemplarSlice, +} + +var countField = &primitiveField{ + fieldName: "Count", + originFieldName: "Count", + returnType: "uint64", + defaultVal: "uint64(0)", + testVal: "uint64(17)", +} + +var intSumField = &primitiveField{ + fieldName: "Sum", + originFieldName: "Sum", + returnType: "int64", + defaultVal: "int64(0.0)", + testVal: "int64(1713)", +} + +var doubleSumField = &primitiveField{ + fieldName: "Sum", + originFieldName: "Sum", + returnType: "float64", + defaultVal: "float64(0.0)", + testVal: "float64(17.13)", +} + +var valueInt64Field = &primitiveField{ + fieldName: "Value", + originFieldName: "Value", + returnType: "int64", + defaultVal: "int64(0)", + testVal: "int64(-17)", +} + +var valueFloat64Field = &primitiveField{ + fieldName: "Value", + originFieldName: "Value", + returnType: "float64", + defaultVal: "float64(0.0)", + testVal: "float64(17.13)", +} + +var bucketCountsField = &primitiveField{ + fieldName: "BucketCounts", + originFieldName: "BucketCounts", + returnType: "[]uint64", + defaultVal: "[]uint64(nil)", + testVal: "[]uint64{1, 2, 3}", +} + +var explicitBoundsField = &primitiveField{ + fieldName: "ExplicitBounds", + originFieldName: "ExplicitBounds", + returnType: "[]float64", + defaultVal: "[]float64(nil)", + testVal: "[]float64{1, 2, 3}", +} + +var quantileField = &primitiveField{ + fieldName: "Quantile", + originFieldName: "Quantile", + returnType: "float64", + defaultVal: "float64(0.0)", + testVal: "float64(17.13)", +} + +var isMonotonicField = &primitiveField{ + fieldName: "IsMonotonic", + originFieldName: "IsMonotonic", + returnType: "bool", + defaultVal: "false", + testVal: "true", +} + +var aggregationTemporalityField = &primitiveTypedField{ + fieldName: "AggregationTemporality", + originFieldName: "AggregationTemporality", + returnType: "AggregationTemporality", + rawType: "otlpmetrics.AggregationTemporality", + defaultVal: "AggregationTemporalityUnspecified", + testVal: "AggregationTemporalityCumulative", +} + +var oneofDataField = &oneofField{ + copyFuncName: "copyData", + originFieldName: "Data", + testVal: "&otlpmetrics.Metric_IntGauge{IntGauge: &otlpmetrics.IntGauge{}}", + fillTestName: "IntGauge", +} diff --git a/internal/otel_collector/cmd/pdatagen/internal/resource_structs.go b/internal/otel_collector/cmd/pdatagen/internal/resource_structs.go new file mode 100644 index 00000000000..9e48aa170db --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/resource_structs.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +var resourceFile = &File{ + Name: "resource", + imports: []string{ + `otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1"`, + }, + testImports: []string{ + `"testing"`, + ``, + `"github.com/stretchr/testify/assert"`, + }, + structs: []baseStruct{ + resource, + }, +} + +var resource = &messageValueStruct{ + structName: "Resource", + description: "// Resource information.", + originFullName: "otlpresource.Resource", + fields: []baseField{ + attributes, + }, +} + +var resourceField = &messageValueField{ + fieldName: "Resource", + originFieldName: "Resource", + returnMessage: resource, +} diff --git a/internal/otel_collector/cmd/pdatagen/internal/trace_structs.go b/internal/otel_collector/cmd/pdatagen/internal/trace_structs.go new file mode 100644 index 00000000000..f5c3b739784 --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/internal/trace_structs.go @@ -0,0 +1,259 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +var traceFile = &File{ + Name: "trace", + imports: []string{ + `"go.opentelemetry.io/collector/internal/data"`, + `otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1"`, + `otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1"`, + }, + testImports: []string{ + `"testing"`, + ``, + `"github.com/stretchr/testify/assert"`, + ``, + `otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1"`, + }, + structs: []baseStruct{ + resourceSpansSlice, + resourceSpans, + instrumentationLibrarySpansSlice, + instrumentationLibrarySpans, + spanSlice, + span, + spanEventSlice, + spanEvent, + spanLinkSlice, + spanLink, + spanStatus, + }, +} + +var resourceSpansSlice = &sliceOfPtrs{ + structName: "ResourceSpansSlice", + element: resourceSpans, +} + +var resourceSpans = &messageValueStruct{ + structName: "ResourceSpans", + description: "// InstrumentationLibrarySpans is a collection of spans from a LibraryInstrumentation.", + originFullName: "otlptrace.ResourceSpans", + fields: []baseField{ + resourceField, + &sliceField{ + fieldName: "InstrumentationLibrarySpans", + originFieldName: "InstrumentationLibrarySpans", + returnSlice: instrumentationLibrarySpansSlice, + }, + }, +} + +var instrumentationLibrarySpansSlice = &sliceOfPtrs{ + structName: "InstrumentationLibrarySpansSlice", + element: instrumentationLibrarySpans, +} + +var instrumentationLibrarySpans = &messageValueStruct{ + structName: "InstrumentationLibrarySpans", + description: "// InstrumentationLibrarySpans is a collection of spans from a LibraryInstrumentation.", + originFullName: "otlptrace.InstrumentationLibrarySpans", + fields: []baseField{ + instrumentationLibraryField, + &sliceField{ + fieldName: "Spans", + originFieldName: "Spans", + returnSlice: spanSlice, + }, + }, +} + +var spanSlice = &sliceOfPtrs{ + structName: "SpanSlice", + element: span, +} + +var span = &messageValueStruct{ + structName: "Span", + description: "// Span represents a single operation within a trace.\n" + + "// See Span definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/trace/v1/trace.proto#L37", + originFullName: "otlptrace.Span", + fields: []baseField{ + traceIDField, + spanIDField, + traceStateField, + parentSpanIDField, + nameField, + &primitiveTypedField{ + fieldName: "Kind", + originFieldName: "Kind", + returnType: "SpanKind", + rawType: "otlptrace.Span_SpanKind", + defaultVal: "SpanKindUNSPECIFIED", + testVal: "SpanKindSERVER", + }, + startTimeField, + endTimeField, + attributes, + droppedAttributesCount, + &sliceField{ + fieldName: "Events", + originFieldName: "Events", + returnSlice: spanEventSlice, + }, + &primitiveField{ + fieldName: "DroppedEventsCount", + originFieldName: "DroppedEventsCount", + returnType: "uint32", + defaultVal: "uint32(0)", + testVal: "uint32(17)", + }, + &sliceField{ + fieldName: "Links", + originFieldName: "Links", + returnSlice: spanLinkSlice, + }, + &primitiveField{ + fieldName: "DroppedLinksCount", + originFieldName: "DroppedLinksCount", + returnType: "uint32", + defaultVal: "uint32(0)", + testVal: "uint32(17)", + }, + &messageValueField{ + fieldName: "Status", + originFieldName: "Status", + returnMessage: spanStatus, + }, + }, +} + +var spanEventSlice = &sliceOfPtrs{ + structName: "SpanEventSlice", + element: spanEvent, +} + +var spanEvent = &messageValueStruct{ + structName: "SpanEvent", + description: "// SpanEvent is a time-stamped annotation of the span, consisting of user-supplied\n" + + "// text description and key-value pairs. See OTLP for event definition.", + originFullName: "otlptrace.Span_Event", + fields: []baseField{ + timeField, + nameField, + attributes, + droppedAttributesCount, + }, +} + +var spanLinkSlice = &sliceOfPtrs{ + structName: "SpanLinkSlice", + element: spanLink, +} + +var spanLink = &messageValueStruct{ + structName: "SpanLink", + description: "// SpanLink is a pointer from the current span to another span in the same trace or in a\n" + + "// different trace. See OTLP for link definition.", + originFullName: "otlptrace.Span_Link", + fields: []baseField{ + traceIDField, + spanIDField, + traceStateField, + attributes, + droppedAttributesCount, + }, +} + +var spanStatus = &messageValueStruct{ + structName: "SpanStatus", + description: "// SpanStatus is an optional final status for this span. Semantically when Status wasn't set\n" + + "// it is means span ended without errors and assume Status.Ok (code = 0).", + originFullName: "otlptrace.Status", + fields: []baseField{ + &primitiveTypedField{ + fieldName: "Code", + originFieldName: "Code", + returnType: "StatusCode", + rawType: "otlptrace.Status_StatusCode", + defaultVal: "StatusCode(0)", + testVal: "StatusCode(1)", + // Generate code without setter. Setter will be manually coded since we + // need to also change DeprecatedCode when Code is changed according + // to OTLP spec https://github.com/open-telemetry/opentelemetry-proto/blob/59c488bfb8fb6d0458ad6425758b70259ff4a2bd/opentelemetry/proto/trace/v1/trace.proto#L231 + manualSetter: true, + }, + &primitiveTypedField{ + fieldName: "DeprecatedCode", + originFieldName: "DeprecatedCode", + returnType: "DeprecatedStatusCode", + rawType: "otlptrace.Status_DeprecatedStatusCode", + defaultVal: "DeprecatedStatusCode(0)", + testVal: "DeprecatedStatusCode(1)", + }, + &primitiveField{ + fieldName: "Message", + originFieldName: "Message", + returnType: "string", + defaultVal: `""`, + testVal: `"cancelled"`, + }, + }, +} + +var traceIDField = &primitiveTypedField{ + fieldName: "TraceID", + originFieldName: "TraceId", + returnType: "TraceID", + rawType: "data.TraceID", + defaultVal: "NewTraceID([16]byte{})", + testVal: "NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})", +} + +var spanIDField = &primitiveTypedField{ + fieldName: "SpanID", + originFieldName: "SpanId", + returnType: "SpanID", + rawType: "data.SpanID", + defaultVal: "NewSpanID([8]byte{})", + testVal: "NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})", +} + +var parentSpanIDField = &primitiveTypedField{ + fieldName: "ParentSpanID", + originFieldName: "ParentSpanId", + returnType: "SpanID", + rawType: "data.SpanID", + defaultVal: "NewSpanID([8]byte{})", + testVal: "NewSpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1})", +} + +var traceStateField = &primitiveTypedField{ + fieldName: "TraceState", + originFieldName: "TraceState", + returnType: "TraceState", + rawType: "string", + defaultVal: `TraceState("")`, + testVal: `TraceState("congo=congos")`, +} + +var droppedAttributesCount = &primitiveField{ + fieldName: "DroppedAttributesCount", + originFieldName: "DroppedAttributesCount", + returnType: "uint32", + defaultVal: "uint32(0)", + testVal: "uint32(17)", +} diff --git a/internal/otel_collector/cmd/pdatagen/main.go b/internal/otel_collector/cmd/pdatagen/main.go new file mode 100644 index 00000000000..02a4fcabad4 --- /dev/null +++ b/internal/otel_collector/cmd/pdatagen/main.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "os" + + "go.opentelemetry.io/collector/cmd/pdatagen/internal" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + +func main() { + for _, fp := range internal.AllFiles { + f, err := os.Create("./consumer/pdata/generated_" + fp.Name + ".go") + check(err) + _, err = f.WriteString(fp.GenerateFile()) + check(err) + check(f.Close()) + f, err = os.Create("./consumer/pdata/generated_" + fp.Name + "_test.go") + check(err) + _, err = f.WriteString(fp.GenerateTestFile()) + check(err) + check(f.Close()) + } +} diff --git a/internal/otel_collector/component/component.go b/internal/otel_collector/component/component.go new file mode 100644 index 00000000000..c591879b51f --- /dev/null +++ b/internal/otel_collector/component/component.go @@ -0,0 +1,135 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Component is either a receiver, exporter, processor or extension. +type Component interface { + // Start tells the component to start. Host parameter can be used for communicating + // with the host after Start() has already returned. If error is returned by + // Start() then the collector startup will be aborted. + // If this is an exporter component it may prepare for exporting + // by connecting to the endpoint. + // + // If the component needs to perform a long-running starting operation then it is recommended + // that Start() returns quickly and the long-running operation is performed in background. + // In that case make sure that the long-running operation does not use the context passed + // to Start() function since that context will be cancelled soon and can abort the long-running operation. + // Create a new context from the context.Background() for long-running operations. + Start(ctx context.Context, host Host) error + + // Shutdown is invoked during service shutdown. + // + // If there are any background operations running by the component they must be aborted as soon as possible. + // Remember that if you started any long-running background operation from the Start() method that operation + // must be also cancelled. + Shutdown(ctx context.Context) error +} + +// Kind specified one of the 4 components kinds, see consts below. +type Kind int + +const ( + _ Kind = iota // skip 0, start types from 1. + KindReceiver + KindProcessor + KindExporter + KindExtension +) + +// Host represents the entity that is hosting a Component. It is used to allow communication +// between the Component and its host (normally the service.Application is the host). +type Host interface { + // ReportFatalError is used to report to the host that the extension + // encountered a fatal error (i.e.: an error that the instance can't recover + // from) after its start function had already returned. + ReportFatalError(err error) + + // GetFactory of the specified kind. Returns the factory for a component type. + // This allows components to create other components. For example: + // func (r MyReceiver) Start(host component.Host) error { + // apacheFactory := host.GetFactory(KindReceiver,"apache").(component.ReceiverFactory) + // receiver, err := apacheFactory.CreateMetricsReceiver(...) + // ... + // } + // GetFactory can be called by the component anytime after Start() begins and + // until Shutdown() is called. Note that the component is responsible for destroying + // other components that it creates. + GetFactory(kind Kind, componentType configmodels.Type) Factory + + // Return map of extensions. Only enabled and created extensions will be returned. + // Typically is used to find an extension by type or by full config name. Both cases + // can be done by iterating the returned map. There are typically very few extensions + // so there there is no performance implications due to iteration. + GetExtensions() map[configmodels.Extension]ServiceExtension + + // Return map of exporters. Only enabled and created exporters will be returned. + // Typically is used to find exporters by type or by full config name. Both cases + // can be done by iterating the returned map. There are typically very few exporters + // so there there is no performance implications due to iteration. + // This returns a map by DataType of maps by exporter configs to the exporter instance. + // Note that an exporter with the same name may be attached to multiple pipelines and + // thus we may have an instance of the exporter for multiple data types. + // This is an experimental function that may change or even be removed completely. + GetExporters() map[configmodels.DataType]map[configmodels.Exporter]Exporter +} + +// Factory interface must be implemented by all component factories. +type Factory interface { + // Type gets the type of the component created by this factory. + Type() configmodels.Type +} + +// ConfigUnmarshaler interface is an optional interface that if implemented by a Factory, +// the configuration loading system will use to unmarshal the config. +type ConfigUnmarshaler interface { + // Unmarshal is a function that un-marshals a viper data into a config struct in a custom way. + // componentViperSection *viper.Viper + // The config for this specific component. May be nil or empty if no config available. + // intoCfg interface{} + // An empty interface wrapping a pointer to the config struct to unmarshal into. + Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error +} + +// CustomUnmarshaler is a function that un-marshals a viper data into a config struct +// in a custom way. +// componentViperSection *viper.Viper +// The config for this specific component. May be nil or empty if no config available. +// intoCfg interface{} +// An empty interface wrapping a pointer to the config struct to unmarshal into. +type CustomUnmarshaler func(componentViperSection *viper.Viper, intoCfg interface{}) error + +// ApplicationStartInfo is the information that is logged at the application start and +// passed into each component. This information can be overridden in custom builds. +type ApplicationStartInfo struct { + // Executable file name, e.g. "otelcol". + ExeName string + + // Long name, used e.g. in the logs. + LongName string + + // Version string. + Version string + + // Git hash of the source code. + GitHash string +} diff --git a/internal/otel_collector/component/component_test.go b/internal/otel_collector/component/component_test.go new file mode 100644 index 00000000000..3c3e46002d9 --- /dev/null +++ b/internal/otel_collector/component/component_test.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component diff --git a/internal/otel_collector/component/componenterror/errors.go b/internal/otel_collector/component/componenterror/errors.go new file mode 100644 index 00000000000..0a89c29b3f2 --- /dev/null +++ b/internal/otel_collector/component/componenterror/errors.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package oterr provides helper functions to create and process +// OpenTelemetry specific errors +package componenterror + +import ( + "errors" + "fmt" + "strings" + + "go.opentelemetry.io/collector/consumer/consumererror" +) + +var ( + // ErrAlreadyStarted indicates an error on starting an already-started component. + ErrAlreadyStarted = errors.New("already started") + + // ErrAlreadyStopped indicates an error on stoping an already-stopped component. + ErrAlreadyStopped = errors.New("already stopped") + + // ErrNilNextConsumer indicates an error on nil next consumer. + ErrNilNextConsumer = errors.New("nil nextConsumer") +) + +// CombineErrors converts a list of errors into one error. +func CombineErrors(errs []error) error { + numErrors := len(errs) + if numErrors == 0 { + // No errors + return nil + } + + if numErrors == 1 { + return errs[0] + } + + errMsgs := make([]string, 0, numErrors) + permanent := false + for _, err := range errs { + if !permanent && consumererror.IsPermanent(err) { + permanent = true + } + errMsgs = append(errMsgs, err.Error()) + } + err := fmt.Errorf("[%s]", strings.Join(errMsgs, "; ")) + if permanent { + err = consumererror.Permanent(err) + } + return err +} diff --git a/internal/otel_collector/component/componenterror/errors_test.go b/internal/otel_collector/component/componenterror/errors_test.go new file mode 100644 index 00000000000..c0c7b8e7bd8 --- /dev/null +++ b/internal/otel_collector/component/componenterror/errors_test.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenterror_test + +import ( + "fmt" + "testing" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer/consumererror" +) + +func TestCombineErrors(t *testing.T) { + testCases := []struct { + errors []error + expected string + expectNil bool + expectedPermanent bool + }{ + { + errors: []error{}, + expectNil: true, + }, + { + errors: []error{ + fmt.Errorf("foo"), + }, + expected: "foo", + }, + { + errors: []error{ + fmt.Errorf("foo"), + fmt.Errorf("bar"), + }, + expected: "[foo; bar]", + }, + { + errors: []error{ + fmt.Errorf("foo"), + fmt.Errorf("bar"), + consumererror.Permanent(fmt.Errorf("permanent"))}, + expected: "Permanent error: [foo; bar; Permanent error: permanent]", + }, + } + + for _, tc := range testCases { + got := componenterror.CombineErrors(tc.errors) + if (got == nil) != tc.expectNil { + t.Errorf("CombineErrors(%v) == nil? Got: %t. Want: %t", tc.errors, got == nil, tc.expectNil) + } + if got != nil && tc.expected != got.Error() { + t.Errorf("CombineErrors(%v) = %q. Want: %q", tc.errors, got, tc.expected) + } + if tc.expectedPermanent && !consumererror.IsPermanent(got) { + t.Errorf("CombineErrors(%v) = %q. Want: consumererror.permanent", tc.errors, got) + } + } +} diff --git a/internal/otel_collector/component/componenthelper/component.go b/internal/otel_collector/component/componenthelper/component.go new file mode 100644 index 00000000000..0ecc6e5a610 --- /dev/null +++ b/internal/otel_collector/component/componenthelper/component.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenthelper + +import ( + "context" + + "go.opentelemetry.io/collector/component" +) + +// Start specifies the function invoked when the exporter is being started. +type Start func(context.Context, component.Host) error + +// Shutdown specifies the function invoked when the exporter is being shutdown. +type Shutdown func(context.Context) error + +// ComponentSettings represents a settings struct to create components. +type ComponentSettings struct { + Start + Shutdown +} + +// DefaultComponentSettings returns the default settings for a component. The Start and Shutdown are no-op. +func DefaultComponentSettings() *ComponentSettings { + return &ComponentSettings{ + Start: func(ctx context.Context, host component.Host) error { return nil }, + Shutdown: func(ctx context.Context) error { return nil }, + } +} + +type baseComponent struct { + start Start + shutdown Shutdown +} + +// Start all senders and exporter and is invoked during service start. +func (be *baseComponent) Start(ctx context.Context, host component.Host) error { + return be.start(ctx, host) +} + +// Shutdown all senders and exporter and is invoked during service shutdown. +func (be *baseComponent) Shutdown(ctx context.Context) error { + return be.shutdown(ctx) +} + +// NewComponent returns a component.Component that calls the given Start and Shutdown. +func NewComponent(s *ComponentSettings) component.Component { + return &baseComponent{ + start: s.Start, + shutdown: s.Shutdown, + } +} diff --git a/internal/otel_collector/component/componenthelper/component_test.go b/internal/otel_collector/component/componenthelper/component_test.go new file mode 100644 index 00000000000..3d4ee6cfb49 --- /dev/null +++ b/internal/otel_collector/component/componenthelper/component_test.go @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenthelper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" +) + +func TestDefaultSettings(t *testing.T) { + st := DefaultComponentSettings() + require.NotNil(t, st) + cp := NewComponent(st) + require.NoError(t, cp.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, cp.Shutdown(context.Background())) +} + +func TestWithStart(t *testing.T) { + startCalled := false + st := DefaultComponentSettings() + st.Start = func(context.Context, component.Host) error { startCalled = true; return nil } + cp := NewComponent(st) + assert.NoError(t, cp.Start(context.Background(), componenttest.NewNopHost())) + assert.True(t, startCalled) +} + +func TestWithStart_ReturnError(t *testing.T) { + want := errors.New("my_error") + st := DefaultComponentSettings() + st.Start = func(context.Context, component.Host) error { return want } + cp := NewComponent(st) + assert.Equal(t, want, cp.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestWithShutdown(t *testing.T) { + shutdownCalled := false + st := DefaultComponentSettings() + st.Shutdown = func(context.Context) error { shutdownCalled = true; return nil } + cp := NewComponent(st) + assert.NoError(t, cp.Shutdown(context.Background())) + assert.True(t, shutdownCalled) +} + +func TestWithShutdown_ReturnError(t *testing.T) { + want := errors.New("my_error") + st := DefaultComponentSettings() + st.Shutdown = func(context.Context) error { return want } + cp := NewComponent(st) + assert.Equal(t, want, cp.Shutdown(context.Background())) +} diff --git a/internal/otel_collector/component/componenttest/application_start_info.go b/internal/otel_collector/component/componenttest/application_start_info.go new file mode 100644 index 00000000000..27f48f2d46c --- /dev/null +++ b/internal/otel_collector/component/componenttest/application_start_info.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/internal/version" +) + +func TestApplicationStartInfo() component.ApplicationStartInfo { + return component.ApplicationStartInfo{ + ExeName: "otelcol", + LongName: "InProcess Collector", + Version: version.Version, + GitHash: version.GitHash, + } +} diff --git a/internal/otel_collector/component/componenttest/doc.go b/internal/otel_collector/component/componenttest/doc.go new file mode 100644 index 00000000000..1762446116c --- /dev/null +++ b/internal/otel_collector/component/componenttest/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package componenttest define types and functions used to help test packages +// implementing the component package interfaces. +package componenttest diff --git a/internal/otel_collector/component/componenttest/docs.go b/internal/otel_collector/component/componenttest/docs.go new file mode 100644 index 00000000000..1bb45bc6566 --- /dev/null +++ b/internal/otel_collector/component/componenttest/docs.go @@ -0,0 +1,102 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "fmt" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" +) + +const ( + readMeFileName = "README.md" +) + +// CheckDocs returns an error if README.md for at least one +// enabled component is missing. "projectPath" is the absolute path to the root +// of the project to which the components belong. "defaultComponentsFilePath" is +// the path to the file that contains imports to all required components, +// "goModule" is the Go module to which the imports belong. This method is intended +// to be used only to verify documentation in Opentelemetry core and contrib +// repositories. Examples, +// 1) Usage in the core repo: +// +// componenttest.CheckDocs( +// "path/to/project", +// "service/defaultcomponents/defaults.go", +// "go.opentelemetry.io/collector", +// ) +// +// 2) Usage in the contrib repo: +// componenttest.CheckDocs( +// "path/to/project", +// "cmd/otelcontrib/components.go", +// "github.com/open-telemetry/opentelemetry-collector-contrib", +// ). +func CheckDocs(projectPath string, relativeComponentsPath string, projectGoModule string) error { + defaultComponentsFilePath := filepath.Join(projectPath, relativeComponentsPath) + _, err := os.Stat(defaultComponentsFilePath) + if err != nil { + return fmt.Errorf("failed to load file %s: %v", defaultComponentsFilePath, err) + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, defaultComponentsFilePath, nil, parser.ImportsOnly) + if err != nil { + return fmt.Errorf("failed to load imports: %v", err) + } + + importPrefixesToCheck := getImportPrefixesToCheck(projectGoModule) + + for _, i := range f.Imports { + importPath := strings.Trim(i.Path.Value, `"`) + + if isComponentImport(importPath, importPrefixesToCheck) { + relativeComponentPath := strings.Replace(importPath, projectGoModule, "", 1) + readmePath := filepath.Join(projectPath, relativeComponentPath, readMeFileName) + _, err := os.Stat(readmePath) + if err != nil { + return fmt.Errorf("README does not exist at %s, add one", readmePath) + } + } + } + return nil +} + +var componentTypes = []string{"extension", "receiver", "processor", "exporter"} + +// getImportPrefixesToCheck returns a slice of strings that are relevant import +// prefixes for components in the given module. +func getImportPrefixesToCheck(module string) []string { + out := make([]string, len(componentTypes)) + for i, typ := range componentTypes { + out[i] = strings.Join([]string{strings.TrimRight(module, "/"), typ}, "/") + } + return out +} + +// isComponentImport returns true if the import corresponds to a Otel component, +// i.e. an extension, exporter, processor or a receiver. +func isComponentImport(importStr string, importPrefixesToCheck []string) bool { + for _, prefix := range importPrefixesToCheck { + if strings.HasPrefix(importStr, prefix) { + return true + } + } + return false +} diff --git a/internal/otel_collector/component/componenttest/docs_test.go b/internal/otel_collector/component/componenttest/docs_test.go new file mode 100644 index 00000000000..bc5a8ef85cf --- /dev/null +++ b/internal/otel_collector/component/componenttest/docs_test.go @@ -0,0 +1,177 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsComponentImport(t *testing.T) { + type args struct { + importStr string + importPrefixesToCheck []string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Match", + args: args{ + importStr: "matching/prefix", + importPrefixesToCheck: []string{ + "some/prefix", + "matching/prefix", + }, + }, + want: true, + }, + { + name: "No match", + args: args{ + importStr: "some/prefix", + importPrefixesToCheck: []string{ + "expecting/prefix", + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isComponentImport(tt.args.importStr, tt.args.importPrefixesToCheck); got != tt.want { + t.Errorf("isComponentImport() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetImportPrefixesToCheck(t *testing.T) { + tests := []struct { + name string + module string + want []string + }{ + { + name: "Get import prefixes - 1", + module: "test", + want: []string{ + "test/extension", + "test/receiver", + "test/processor", + "test/exporter", + }, + }, + { + name: "Get import prefixes - 2", + module: "test/", + want: []string{ + "test/extension", + "test/receiver", + "test/processor", + "test/exporter", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getImportPrefixesToCheck(tt.module); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getImportPrefixesToCheck() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCheckDocs(t *testing.T) { + type args struct { + projectPath string + relativeDefaultComponentsPath string + projectGoModule string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Invalid project path", + args: args{ + projectPath: "invalid/project", + relativeDefaultComponentsPath: "invalid/file", + projectGoModule: "go.opentelemetry.io/collector", + }, + wantErr: true, + }, + { + name: "Valid files", + args: args{ + projectPath: getProjectPath(t), + relativeDefaultComponentsPath: "service/defaultcomponents/defaults.go", + projectGoModule: "go.opentelemetry.io/collector", + }, + wantErr: false, + }, + { + name: "Invalid files", + args: args{ + projectPath: getProjectPath(t), + relativeDefaultComponentsPath: "service/defaultcomponents/invalid.go", + projectGoModule: "go.opentelemetry.io/collector", + }, + wantErr: true, + }, + { + name: "Invalid imports", + args: args{ + projectPath: getProjectPath(t), + relativeDefaultComponentsPath: "component/componenttest/testdata/invalid_go.txt", + projectGoModule: "go.opentelemetry.io/collector", + }, + wantErr: true, + }, + { + name: "README does not exist", + args: args{ + projectPath: getProjectPath(t), + relativeDefaultComponentsPath: "component/componenttest/testdata/valid_go.txt", + projectGoModule: "go.opentelemetry.io/collector", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := CheckDocs(tt.args.projectPath, tt.args.relativeDefaultComponentsPath, tt.args.projectGoModule); (err != nil) != tt.wantErr { + t.Errorf("CheckDocs() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func getProjectPath(t *testing.T) string { + wd, err := os.Getwd() + require.NoError(t, err, "failed to get working directory: %v") + + // Absolute path to the project root directory + projectPath := filepath.Join(wd, "../../") + + return projectPath +} diff --git a/internal/otel_collector/component/componenttest/error_waiting_host.go b/internal/otel_collector/component/componenttest/error_waiting_host.go new file mode 100644 index 00000000000..ee3c16fbeec --- /dev/null +++ b/internal/otel_collector/component/componenttest/error_waiting_host.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" +) + +// ErrorWaitingHost mocks an component.Host for test purposes. +type ErrorWaitingHost struct { + errorChan chan error +} + +var _ component.Host = (*ErrorWaitingHost)(nil) + +// NewErrorWaitingHost returns a new instance of ErrorWaitingHost with proper defaults for most +// tests. +func NewErrorWaitingHost() *ErrorWaitingHost { + return &ErrorWaitingHost{ + errorChan: make(chan error, 1), + } +} + +// ReportFatalError is used to report to the host that the extension encountered +// a fatal error (i.e.: an error that the instance can't recover from) after +// its start function has already returned. +func (ews *ErrorWaitingHost) ReportFatalError(err error) { + ews.errorChan <- err +} + +// WaitForFatalError waits the given amount of time until an error is reported via +// ReportFatalError. It returns the error, if any, and a bool to indicated if +// an error was received before the time out. +func (ews *ErrorWaitingHost) WaitForFatalError(timeout time.Duration) (receivedError bool, err error) { + select { + case err = <-ews.errorChan: + receivedError = true + case <-time.After(timeout): + } + + return +} + +// GetFactory of the specified kind. Returns the factory for a component type. +func (ews *ErrorWaitingHost) GetFactory(_ component.Kind, _ configmodels.Type) component.Factory { + return nil +} + +func (ews *ErrorWaitingHost) GetExtensions() map[configmodels.Extension]component.ServiceExtension { + return nil +} + +func (ews *ErrorWaitingHost) GetExporters() map[configmodels.DataType]map[configmodels.Exporter]component.Exporter { + return nil +} diff --git a/internal/otel_collector/component/componenttest/error_waiting_host_test.go b/internal/otel_collector/component/componenttest/error_waiting_host_test.go new file mode 100644 index 00000000000..513c608cba4 --- /dev/null +++ b/internal/otel_collector/component/componenttest/error_waiting_host_test.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" +) + +func TestNewErrorWaitingHost(t *testing.T) { + mh := NewErrorWaitingHost() + require.NotNil(t, mh) + + reportedErr := errors.New("TestError") + go mh.ReportFatalError(reportedErr) + + receivedError, receivedErr := mh.WaitForFatalError(100 * time.Millisecond) + require.True(t, receivedError) + require.Equal(t, reportedErr, receivedErr) + + receivedError, _ = mh.WaitForFatalError(100 * time.Millisecond) + require.False(t, receivedError) +} + +func TestNewErrorWaitingHost_Noop(t *testing.T) { + mh := NewErrorWaitingHost() + require.NotNil(t, mh) + + assert.Nil(t, mh.GetExporters()) + assert.Nil(t, mh.GetExtensions()) + assert.Nil(t, mh.GetFactory(component.KindReceiver, "test")) +} diff --git a/internal/otel_collector/component/componenttest/example_factories.go b/internal/otel_collector/component/componenttest/example_factories.go new file mode 100644 index 00000000000..1eda999ddd1 --- /dev/null +++ b/internal/otel_collector/component/componenttest/example_factories.go @@ -0,0 +1,508 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "context" + "fmt" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// ExampleReceiver is for testing purposes. We are defining an example config and factory +// for "examplereceiver" receiver type. +type ExampleReceiver struct { + configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + // Configures the receiver server protocol. + confignet.TCPAddr `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + ExtraSetting string `mapstructure:"extra"` + ExtraMapSetting map[string]string `mapstructure:"extra_map"` + ExtraListSetting []string `mapstructure:"extra_list"` + + // FailTraceCreation causes CreateTracesReceiver to fail. Useful for testing. + FailTraceCreation bool `mapstructure:"-"` + + // FailMetricsCreation causes CreateMetricsReceiver to fail. Useful for testing. + FailMetricsCreation bool `mapstructure:"-"` +} + +// ExampleReceiverFactory is factory for ExampleReceiver. +type ExampleReceiverFactory struct { +} + +var _ component.ReceiverFactory = (*ExampleReceiverFactory)(nil) + +// Type gets the type of the Receiver config created by this factory. +func (f *ExampleReceiverFactory) Type() configmodels.Type { + return "examplereceiver" +} + +// CreateDefaultConfig creates the default configuration for the Receiver. +func (f *ExampleReceiverFactory) CreateDefaultConfig() configmodels.Receiver { + return &ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: f.Type(), + NameVal: string(f.Type()), + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:1000", + }, + ExtraSetting: "some string", + ExtraMapSetting: nil, + ExtraListSetting: nil, + } +} + +// CustomUnmarshaler implements the deprecated way to provide custom unmarshalers. +func (f *ExampleReceiverFactory) CustomUnmarshaler() component.CustomUnmarshaler { + return nil +} + +// CreateTraceReceiver creates a trace receiver based on this config. +func (f *ExampleReceiverFactory) CreateTracesReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer, +) (component.TracesReceiver, error) { + if cfg.(*ExampleReceiver).FailTraceCreation { + return nil, configerror.ErrDataTypeIsNotSupported + } + + receiver := f.createReceiver(cfg) + receiver.TraceConsumer = nextConsumer + + return receiver, nil +} + +func (f *ExampleReceiverFactory) createReceiver(cfg configmodels.Receiver) *ExampleReceiverProducer { + // There must be one receiver for all data types. We maintain a map of + // receivers per config. + + // Check to see if there is already a receiver for this config. + receiver, ok := exampleReceivers[cfg] + if !ok { + receiver = &ExampleReceiverProducer{} + // Remember the receiver in the map + exampleReceivers[cfg] = receiver + } + + return receiver +} + +// CreateMetricsReceiver creates a metrics receiver based on this config. +func (f *ExampleReceiverFactory) CreateMetricsReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsReceiver, error) { + if cfg.(*ExampleReceiver).FailMetricsCreation { + return nil, configerror.ErrDataTypeIsNotSupported + } + + receiver := f.createReceiver(cfg) + receiver.MetricsConsumer = nextConsumer + + return receiver, nil +} + +func (f *ExampleReceiverFactory) CreateLogsReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.LogsConsumer, +) (component.LogsReceiver, error) { + receiver := f.createReceiver(cfg) + receiver.LogConsumer = nextConsumer + + return receiver, nil +} + +// ExampleReceiverProducer allows producing traces and metrics for testing purposes. +type ExampleReceiverProducer struct { + Started bool + Stopped bool + TraceConsumer consumer.TracesConsumer + MetricsConsumer consumer.MetricsConsumer + LogConsumer consumer.LogsConsumer +} + +// Start tells the receiver to start its processing. +func (erp *ExampleReceiverProducer) Start(_ context.Context, _ component.Host) error { + erp.Started = true + return nil +} + +// Shutdown tells the receiver that should stop reception, +func (erp *ExampleReceiverProducer) Shutdown(context.Context) error { + erp.Stopped = true + return nil +} + +// This is the map of already created example receivers for particular configurations. +// We maintain this map because the ReceiverFactory is asked trace and metric receivers separately +// when it gets CreateTracesReceiver() and CreateMetricsReceiver() but they must not +// create separate objects, they must use one Receiver object per configuration. +var exampleReceivers = map[configmodels.Receiver]*ExampleReceiverProducer{} + +// MultiProtoReceiver is for testing purposes. We are defining an example multi protocol +// config and factory for "multireceiver" receiver type. +type MultiProtoReceiver struct { + configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + Protocols map[string]MultiProtoReceiverOneCfg `mapstructure:"protocols"` +} + +// MultiProtoReceiverOneCfg is multi proto receiver config. +type MultiProtoReceiverOneCfg struct { + Endpoint string `mapstructure:"endpoint"` + ExtraSetting string `mapstructure:"extra"` +} + +// MultiProtoReceiverFactory is factory for MultiProtoReceiver. +type MultiProtoReceiverFactory struct { +} + +var _ component.ReceiverFactory = (*MultiProtoReceiverFactory)(nil) + +// Type gets the type of the Receiver config created by this factory. +func (f *MultiProtoReceiverFactory) Type() configmodels.Type { + return "multireceiver" +} + +// Unmarshal implements the ConfigUnmarshaler interface. +func (f *MultiProtoReceiverFactory) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error { + return componentViperSection.UnmarshalExact(intoCfg) +} + +// CreateDefaultConfig creates the default configuration for the Receiver. +func (f *MultiProtoReceiverFactory) CreateDefaultConfig() configmodels.Receiver { + return &MultiProtoReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: f.Type(), + NameVal: string(f.Type()), + }, + Protocols: map[string]MultiProtoReceiverOneCfg{ + "http": { + Endpoint: "example.com:8888", + ExtraSetting: "extra string 1", + }, + "tcp": { + Endpoint: "omnition.com:9999", + ExtraSetting: "extra string 2", + }, + }, + } +} + +// CreateTraceReceiver creates a trace receiver based on this config. +func (f *MultiProtoReceiverFactory) CreateTracesReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + _ configmodels.Receiver, + _ consumer.TracesConsumer, +) (component.TracesReceiver, error) { + // Not used for this test, just return nil + return nil, nil +} + +// CreateMetricsReceiver creates a metrics receiver based on this config. +func (f *MultiProtoReceiverFactory) CreateMetricsReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + _ configmodels.Receiver, + _ consumer.MetricsConsumer, +) (component.MetricsReceiver, error) { + // Not used for this test, just return nil + return nil, nil +} + +// CreateMetricsReceiver creates a metrics receiver based on this config. +func (f *MultiProtoReceiverFactory) CreateLogsReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + _ configmodels.Receiver, + _ consumer.LogsConsumer, +) (component.LogsReceiver, error) { + // Not used for this test, just return nil + return nil, nil +} + +// ExampleExporter is for testing purposes. We are defining an example config and factory +// for "exampleexporter" exporter type. +type ExampleExporter struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + ExtraInt int32 `mapstructure:"extra_int"` + ExtraSetting string `mapstructure:"extra"` + ExtraMapSetting map[string]string `mapstructure:"extra_map"` + ExtraListSetting []string `mapstructure:"extra_list"` +} + +// ExampleExporterFactory is factory for ExampleExporter. +type ExampleExporterFactory struct { +} + +// Type gets the type of the Exporter config created by this factory. +func (f *ExampleExporterFactory) Type() configmodels.Type { + return "exampleexporter" +} + +// CreateDefaultConfig creates the default configuration for the Exporter. +func (f *ExampleExporterFactory) CreateDefaultConfig() configmodels.Exporter { + return &ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: f.Type(), + NameVal: string(f.Type()), + }, + ExtraSetting: "some export string", + ExtraMapSetting: nil, + ExtraListSetting: nil, + } +} + +// CustomUnmarshaler implements the deprecated way to provide custom unmarshalers. +func (f *ExampleExporterFactory) CustomUnmarshaler() component.CustomUnmarshaler { + return func(componentViperSection *viper.Viper, intoCfg interface{}) error { + return componentViperSection.UnmarshalExact(intoCfg) + } +} + +// CreateTraceExporter creates a trace exporter based on this config. +func (f *ExampleExporterFactory) CreateTracesExporter( + _ context.Context, + _ component.ExporterCreateParams, + _ configmodels.Exporter, +) (component.TracesExporter, error) { + return &ExampleExporterConsumer{}, nil +} + +// CreateMetricsExporter creates a metrics exporter based on this config. +func (f *ExampleExporterFactory) CreateMetricsExporter( + _ context.Context, + _ component.ExporterCreateParams, + _ configmodels.Exporter, +) (component.MetricsExporter, error) { + return &ExampleExporterConsumer{}, nil +} + +func (f *ExampleExporterFactory) CreateLogsExporter( + _ context.Context, + _ component.ExporterCreateParams, + _ configmodels.Exporter, +) (component.LogsExporter, error) { + return &ExampleExporterConsumer{}, nil +} + +// ExampleExporterConsumer stores consumed traces and metrics for testing purposes. +type ExampleExporterConsumer struct { + Traces []pdata.Traces + Metrics []pdata.Metrics + Logs []pdata.Logs + ExporterStarted bool + ExporterShutdown bool +} + +// Start tells the exporter to start. The exporter may prepare for exporting +// by connecting to the endpoint. Host parameter can be used for communicating +// with the host after Start() has already returned. +func (exp *ExampleExporterConsumer) Start(_ context.Context, _ component.Host) error { + exp.ExporterStarted = true + return nil +} + +// ConsumeTraceData receives consumerdata.TraceData for processing by the TracesConsumer. +func (exp *ExampleExporterConsumer) ConsumeTraces(_ context.Context, td pdata.Traces) error { + exp.Traces = append(exp.Traces, td) + return nil +} + +// ConsumeMetricsData receives consumerdata.MetricsData for processing by the MetricsConsumer. +func (exp *ExampleExporterConsumer) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + exp.Metrics = append(exp.Metrics, md) + return nil +} + +func (exp *ExampleExporterConsumer) ConsumeLogs(_ context.Context, ld pdata.Logs) error { + exp.Logs = append(exp.Logs, ld) + return nil +} + +// Shutdown is invoked during shutdown. +func (exp *ExampleExporterConsumer) Shutdown(context.Context) error { + exp.ExporterShutdown = true + return nil +} + +// ExampleProcessorCfg is for testing purposes. We are defining an example config and factory +// for "exampleprocessor" processor type. +type ExampleProcessorCfg struct { + configmodels.ProcessorSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + ExtraSetting string `mapstructure:"extra"` + ExtraMapSetting map[string]string `mapstructure:"extra_map"` + ExtraListSetting []string `mapstructure:"extra_list"` +} + +// ExampleProcessorFactory is factory for ExampleProcessor. +type ExampleProcessorFactory struct { +} + +// Type gets the type of the Processor config created by this factory. +func (f *ExampleProcessorFactory) Type() configmodels.Type { + return "exampleprocessor" +} + +// CreateDefaultConfig creates the default configuration for the Processor. +func (f *ExampleProcessorFactory) CreateDefaultConfig() configmodels.Processor { + return &ExampleProcessorCfg{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: f.Type(), + NameVal: string(f.Type()), + }, + ExtraSetting: "some export string", + ExtraMapSetting: nil, + ExtraListSetting: nil, + } +} + +// CreateTraceProcessor creates a trace processor based on this config. +func (f *ExampleProcessorFactory) CreateTracesProcessor(ctx context.Context, params component.ProcessorCreateParams, cfg configmodels.Processor, nextConsumer consumer.TracesConsumer) (component.TracesProcessor, error) { + return &ExampleProcessor{nextTraces: nextConsumer}, nil +} + +// CreateMetricsProcessor creates a metrics processor based on this config. +func (f *ExampleProcessorFactory) CreateMetricsProcessor(ctx context.Context, params component.ProcessorCreateParams, cfg configmodels.Processor, nextConsumer consumer.MetricsConsumer) (component.MetricsProcessor, error) { + return &ExampleProcessor{nextMetrics: nextConsumer}, nil +} + +func (f *ExampleProcessorFactory) CreateLogsProcessor( + _ context.Context, + _ component.ProcessorCreateParams, + _ configmodels.Processor, + nextConsumer consumer.LogsConsumer, +) (component.LogsProcessor, error) { + return &ExampleProcessor{nextLogs: nextConsumer}, nil +} + +type ExampleProcessor struct { + nextTraces consumer.TracesConsumer + nextMetrics consumer.MetricsConsumer + nextLogs consumer.LogsConsumer +} + +func (ep *ExampleProcessor) Start(_ context.Context, _ component.Host) error { + return nil +} + +func (ep *ExampleProcessor) Shutdown(_ context.Context) error { + return nil +} + +func (ep *ExampleProcessor) GetCapabilities() component.ProcessorCapabilities { + return component.ProcessorCapabilities{MutatesConsumedData: false} +} + +func (ep *ExampleProcessor) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + return ep.nextTraces.ConsumeTraces(ctx, td) +} + +func (ep *ExampleProcessor) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + return ep.nextMetrics.ConsumeMetrics(ctx, md) +} + +func (ep *ExampleProcessor) ConsumeLogs(ctx context.Context, ld pdata.Logs) error { + return ep.nextLogs.ConsumeLogs(ctx, ld) +} + +// ExampleExtensionCfg is for testing purposes. We are defining an example config and factory +// for "exampleextension" extension type. +type ExampleExtensionCfg struct { + configmodels.ExtensionSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + ExtraSetting string `mapstructure:"extra"` + ExtraMapSetting map[string]string `mapstructure:"extra_map"` + ExtraListSetting []string `mapstructure:"extra_list"` +} + +type ExampleExtension struct { +} + +func (e *ExampleExtension) Start(_ context.Context, _ component.Host) error { return nil } + +func (e *ExampleExtension) Shutdown(_ context.Context) error { return nil } + +// ExampleExtensionFactory is factory for ExampleExtensionCfg. +type ExampleExtensionFactory struct { + FailCreation bool +} + +// Type gets the type of the Extension config created by this factory. +func (f *ExampleExtensionFactory) Type() configmodels.Type { + return "exampleextension" +} + +// CreateDefaultConfig creates the default configuration for the Extension. +func (f *ExampleExtensionFactory) CreateDefaultConfig() configmodels.Extension { + return &ExampleExtensionCfg{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: f.Type(), + NameVal: string(f.Type()), + }, + ExtraSetting: "extra string setting", + ExtraMapSetting: nil, + ExtraListSetting: nil, + } +} + +// CreateExtension creates an Extension based on this config. +func (f *ExampleExtensionFactory) CreateExtension(_ context.Context, _ component.ExtensionCreateParams, _ configmodels.Extension) (component.ServiceExtension, error) { + if f.FailCreation { + return nil, fmt.Errorf("cannot create %q extension type", f.Type()) + } + return &ExampleExtension{}, nil +} + +// ExampleComponents registers example factories. This is only used by tests. +func ExampleComponents() ( + factories component.Factories, + err error, +) { + if factories.Extensions, err = component.MakeExtensionFactoryMap(&ExampleExtensionFactory{}); err != nil { + return + } + + factories.Receivers, err = component.MakeReceiverFactoryMap( + &ExampleReceiverFactory{}, + &MultiProtoReceiverFactory{}, + ) + if err != nil { + return + } + + factories.Exporters, err = component.MakeExporterFactoryMap(&ExampleExporterFactory{}) + if err != nil { + return + } + + factories.Processors, err = component.MakeProcessorFactoryMap(&ExampleProcessorFactory{}) + + return +} diff --git a/internal/otel_collector/component/componenttest/example_factories_test.go b/internal/otel_collector/component/componenttest/example_factories_test.go new file mode 100644 index 00000000000..77b2c467d6a --- /dev/null +++ b/internal/otel_collector/component/componenttest/example_factories_test.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestExampleExporterConsumer(t *testing.T) { + exp := &ExampleExporterConsumer{} + host := NewNopHost() + assert.False(t, exp.ExporterStarted) + err := exp.Start(context.Background(), host) + assert.NoError(t, err) + assert.True(t, exp.ExporterStarted) + + assert.Equal(t, 0, len(exp.Traces)) + err = exp.ConsumeTraces(context.Background(), pdata.Traces{}) + assert.NoError(t, err) + assert.Equal(t, 1, len(exp.Traces)) + + assert.Equal(t, 0, len(exp.Metrics)) + err = exp.ConsumeMetrics(context.Background(), pdata.Metrics{}) + assert.NoError(t, err) + assert.Equal(t, 1, len(exp.Metrics)) + + assert.False(t, exp.ExporterShutdown) + err = exp.Shutdown(context.Background()) + assert.NoError(t, err) + assert.True(t, exp.ExporterShutdown) +} + +func TestExampleReceiverProducer(t *testing.T) { + rcv := &ExampleReceiverProducer{} + host := NewNopHost() + assert.False(t, rcv.Started) + err := rcv.Start(context.Background(), host) + assert.NoError(t, err) + assert.True(t, rcv.Started) + + err = rcv.Shutdown(context.Background()) + assert.NoError(t, err) + assert.True(t, rcv.Started) +} diff --git a/internal/otel_collector/component/componenttest/nop_host.go b/internal/otel_collector/component/componenttest/nop_host.go new file mode 100644 index 00000000000..3120c06ea9f --- /dev/null +++ b/internal/otel_collector/component/componenttest/nop_host.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" +) + +// NopHost mocks a receiver.ReceiverHost for test purposes. +type NopHost struct { +} + +var _ component.Host = (*NopHost)(nil) + +// NewNopHost returns a new instance of NopHost with proper defaults for most +// tests. +func NewNopHost() component.Host { + return &NopHost{} +} + +// ReportFatalError is used to report to the host that the receiver encountered +// a fatal error (i.e.: an error that the instance can't recover from) after +// its start function has already returned. +func (nh *NopHost) ReportFatalError(_ error) { + // Do nothing for now. +} + +// GetFactory of the specified kind. Returns the factory for a component type. +func (nh *NopHost) GetFactory(_ component.Kind, _ configmodels.Type) component.Factory { + return nil +} + +func (nh *NopHost) GetExtensions() map[configmodels.Extension]component.ServiceExtension { + return nil +} + +func (nh *NopHost) GetExporters() map[configmodels.DataType]map[configmodels.Exporter]component.Exporter { + return nil +} diff --git a/internal/otel_collector/component/componenttest/nop_host_test.go b/internal/otel_collector/component/componenttest/nop_host_test.go new file mode 100644 index 00000000000..6e9eb922af7 --- /dev/null +++ b/internal/otel_collector/component/componenttest/nop_host_test.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package componenttest + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" +) + +func TestNewNopHost(t *testing.T) { + nh := NewNopHost() + require.NotNil(t, nh) + require.IsType(t, &NopHost{}, nh) + + nh.ReportFatalError(errors.New("TestError")) + assert.Nil(t, nh.GetExporters()) + assert.Nil(t, nh.GetExtensions()) + assert.Nil(t, nh.GetFactory(component.KindReceiver, "test")) +} diff --git a/internal/otel_collector/component/componenttest/testdata/invalid_go.txt b/internal/otel_collector/component/componenttest/testdata/invalid_go.txt new file mode 100644 index 00000000000..50ecec8f181 --- /dev/null +++ b/internal/otel_collector/component/componenttest/testdata/invalid_go.txt @@ -0,0 +1,6 @@ +package testdata + + +import ( + "import +) \ No newline at end of file diff --git a/internal/otel_collector/component/componenttest/testdata/valid_go.txt b/internal/otel_collector/component/componenttest/testdata/valid_go.txt new file mode 100644 index 00000000000..2c88748717d --- /dev/null +++ b/internal/otel_collector/component/componenttest/testdata/valid_go.txt @@ -0,0 +1,9 @@ +package testdata + + +import ( + "go.opentelemetry.io/collector/exporter/exporter1" +) + +func main() { +} \ No newline at end of file diff --git a/internal/otel_collector/component/exporter.go b/internal/otel_collector/component/exporter.go new file mode 100644 index 00000000000..d95c520ee22 --- /dev/null +++ b/internal/otel_collector/component/exporter.go @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +// Exporter defines functions that all exporters must implement. +type Exporter interface { + Component +} + +// TracesExporter is a Exporter that can consume traces. +type TracesExporter interface { + Exporter + consumer.TracesConsumer +} + +// MetricsExporter is an Exporter that can consume metrics. +type MetricsExporter interface { + Exporter + consumer.MetricsConsumer +} + +// LogsExporter is an Exporter that can consume logs. +type LogsExporter interface { + Exporter + consumer.LogsConsumer +} + +// ExporterCreateParams is passed to Create*Exporter functions. +type ExporterCreateParams struct { + // Logger that the factory can use during creation and can pass to the created + // component to be used later as well. + Logger *zap.Logger + + // ApplicationStartInfo can be used by components for informational purposes + ApplicationStartInfo ApplicationStartInfo +} + +// ExporterFactory can create TracesExporter and MetricsExporter. This is the +// new factory type that can create new style exporters. +type ExporterFactory interface { + Factory + + // CreateDefaultConfig creates the default configuration for the Exporter. + // This method can be called multiple times depending on the pipeline + // configuration and should not cause side-effects that prevent the creation + // of multiple instances of the Exporter. + // The object returned by this method needs to pass the checks implemented by + // 'configcheck.ValidateConfig'. It is recommended to have such check in the + // tests of any implementation of the Factory interface. + CreateDefaultConfig() configmodels.Exporter + + // CreateTracesExporter creates a trace exporter based on this config. + // If the exporter type does not support tracing or if the config is not valid + // error will be returned instead. + CreateTracesExporter( + ctx context.Context, + params ExporterCreateParams, + cfg configmodels.Exporter, + ) (TracesExporter, error) + + // CreateMetricsExporter creates a metrics exporter based on this config. + // If the exporter type does not support metrics or if the config is not valid + // error will be returned instead. + CreateMetricsExporter( + ctx context.Context, + params ExporterCreateParams, + cfg configmodels.Exporter, + ) (MetricsExporter, error) + + // CreateLogsExporter creates an exporter based on the config. + // If the exporter type does not support logs or if the config is not valid + // error will be returned instead. + CreateLogsExporter( + ctx context.Context, + params ExporterCreateParams, + cfg configmodels.Exporter, + ) (LogsExporter, error) +} diff --git a/internal/otel_collector/component/exporter_test.go b/internal/otel_collector/component/exporter_test.go new file mode 100644 index 00000000000..8c0efe81d85 --- /dev/null +++ b/internal/otel_collector/component/exporter_test.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" +) + +type TestExporterFactory struct { + name string +} + +// Type gets the type of the Exporter config created by this factory. +func (f *TestExporterFactory) Type() configmodels.Type { + return configmodels.Type(f.name) +} + +// CreateDefaultConfig creates the default configuration for the Exporter. +func (f *TestExporterFactory) CreateDefaultConfig() configmodels.Exporter { + return nil +} + +// CreateTraceExporter creates a trace exporter based on this config. +func (f *TestExporterFactory) CreateTracesExporter(context.Context, ExporterCreateParams, configmodels.Exporter) (TracesExporter, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsExporter creates a metrics exporter based on this config. +func (f *TestExporterFactory) CreateMetricsExporter(context.Context, ExporterCreateParams, configmodels.Exporter) (MetricsExporter, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsExporter creates a logs exporter based on this config. +func (f *TestExporterFactory) CreateLogsExporter(context.Context, ExporterCreateParams, configmodels.Exporter) (LogsExporter, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +func TestBuildExporters(t *testing.T) { + type testCase struct { + in []ExporterFactory + out map[configmodels.Type]ExporterFactory + } + + testCases := []testCase{ + { + in: []ExporterFactory{ + &TestExporterFactory{"exp1"}, + &TestExporterFactory{"exp2"}, + }, + out: map[configmodels.Type]ExporterFactory{ + "exp1": &TestExporterFactory{"exp1"}, + "exp2": &TestExporterFactory{"exp2"}, + }, + }, + { + in: []ExporterFactory{ + &TestExporterFactory{"exp1"}, + &TestExporterFactory{"exp1"}, + }, + }, + } + + for _, c := range testCases { + out, err := MakeExporterFactoryMap(c.in...) + if c.out == nil { + assert.Error(t, err) + continue + } + assert.NoError(t, err) + assert.Equal(t, c.out, out) + } +} diff --git a/internal/otel_collector/component/extension.go b/internal/otel_collector/component/extension.go new file mode 100644 index 00000000000..9fb811388b8 --- /dev/null +++ b/internal/otel_collector/component/extension.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// ServiceExtension is the interface for objects hosted by the OpenTelemetry Collector that +// don't participate directly on data pipelines but provide some functionality +// to the service, examples: health check endpoint, z-pages, etc. +type ServiceExtension interface { + Component +} + +// PipelineWatcher is an extra interface for ServiceExtension hosted by the OpenTelemetry +// Collector that is to be implemented by extensions interested in changes to pipeline +// states. Typically this will be used by extensions that change their behavior if data is +// being ingested or not, e.g.: a k8s readiness probe. +type PipelineWatcher interface { + // Ready notifies the ServiceExtension that all pipelines were built and the + // receivers were started, i.e.: the service is ready to receive data + // (notice that it may already have received data when this method is called). + Ready() error + + // NotReady notifies the ServiceExtension that all receivers are about to be stopped, + // i.e.: pipeline receivers will not accept new data. + // This is sent before receivers are stopped, so the ServiceExtension can take any + // appropriate action before that happens. + NotReady() error +} + +// ExtensionCreateParams is passed to ExtensionFactory.Create* functions. +type ExtensionCreateParams struct { + // Logger that the factory can use during creation and can pass to the created + // component to be used later as well. + Logger *zap.Logger + + // ApplicationStartInfo can be used by components for informational purposes + ApplicationStartInfo ApplicationStartInfo +} + +// ExtensionFactory is a factory interface for extensions to the service. +type ExtensionFactory interface { + Factory + + // CreateDefaultConfig creates the default configuration for the Extension. + // This method can be called multiple times depending on the pipeline + // configuration and should not cause side-effects that prevent the creation + // of multiple instances of the Extension. + // The object returned by this method needs to pass the checks implemented by + // 'configcheck.ValidateConfig'. It is recommended to have such check in the + // tests of any implementation of the Factory interface. + CreateDefaultConfig() configmodels.Extension + + // CreateExtension creates a service extension based on the given config. + CreateExtension(ctx context.Context, params ExtensionCreateParams, cfg configmodels.Extension) (ServiceExtension, error) +} diff --git a/internal/otel_collector/component/factories.go b/internal/otel_collector/component/factories.go new file mode 100644 index 00000000000..860b5c57063 --- /dev/null +++ b/internal/otel_collector/component/factories.go @@ -0,0 +1,93 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "fmt" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Factories struct holds in a single type all component factories that +// can be handled by the Config. +type Factories struct { + // Receivers maps receiver type names in the config to the respective factory. + Receivers map[configmodels.Type]ReceiverFactory + + // Processors maps processor type names in the config to the respective factory. + Processors map[configmodels.Type]ProcessorFactory + + // Exporters maps exporter type names in the config to the respective factory. + Exporters map[configmodels.Type]ExporterFactory + + // Extensions maps extension type names in the config to the respective factory. + Extensions map[configmodels.Type]ExtensionFactory +} + +// MakeReceiverFactoryMap takes a list of receiver factories and returns a map +// with factory type as keys. It returns a non-nil error when more than one factories +// have the same type. +func MakeReceiverFactoryMap(factories ...ReceiverFactory) (map[configmodels.Type]ReceiverFactory, error) { + fMap := map[configmodels.Type]ReceiverFactory{} + for _, f := range factories { + if _, ok := fMap[f.Type()]; ok { + return fMap, fmt.Errorf("duplicate receiver factory %q", f.Type()) + } + fMap[f.Type()] = f + } + return fMap, nil +} + +// MakeProcessorFactoryMap takes a list of processor factories and returns a map +// with factory type as keys. It returns a non-nil error when more than one factories +// have the same type. +func MakeProcessorFactoryMap(factories ...ProcessorFactory) (map[configmodels.Type]ProcessorFactory, error) { + fMap := map[configmodels.Type]ProcessorFactory{} + for _, f := range factories { + if _, ok := fMap[f.Type()]; ok { + return fMap, fmt.Errorf("duplicate processor factory %q", f.Type()) + } + fMap[f.Type()] = f + } + return fMap, nil +} + +// MakeExporterFactoryMap takes a list of exporter factories and returns a map +// with factory type as keys. It returns a non-nil error when more than one factories +// have the same type. +func MakeExporterFactoryMap(factories ...ExporterFactory) (map[configmodels.Type]ExporterFactory, error) { + fMap := map[configmodels.Type]ExporterFactory{} + for _, f := range factories { + if _, ok := fMap[f.Type()]; ok { + return fMap, fmt.Errorf("duplicate exporter factory %q", f.Type()) + } + fMap[f.Type()] = f + } + return fMap, nil +} + +// MakeExtensionFactoryMap takes a list of extension factories and returns a map +// with factory type as keys. It returns a non-nil error when more than one factories +// have the same type. +func MakeExtensionFactoryMap(factories ...ExtensionFactory) (map[configmodels.Type]ExtensionFactory, error) { + fMap := map[configmodels.Type]ExtensionFactory{} + for _, f := range factories { + if _, ok := fMap[f.Type()]; ok { + return fMap, fmt.Errorf("duplicate extension factory %q", f.Type()) + } + fMap[f.Type()] = f + } + return fMap, nil +} diff --git a/internal/otel_collector/component/processor.go b/internal/otel_collector/component/processor.go new file mode 100644 index 00000000000..8d7779c6a74 --- /dev/null +++ b/internal/otel_collector/component/processor.go @@ -0,0 +1,116 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +// Processor defines the common functions that must be implemented by TracesProcessor +// and MetricsProcessor. +type Processor interface { + Component + + // GetCapabilities must return the capabilities of the processor. + GetCapabilities() ProcessorCapabilities +} + +// TracesProcessor is a processor that can consume traces. +type TracesProcessor interface { + Processor + consumer.TracesConsumer +} + +// MetricsProcessor is a processor that can consume metrics. +type MetricsProcessor interface { + Processor + consumer.MetricsConsumer +} + +// LogsProcessor is a processor that can consume logs. +type LogsProcessor interface { + Processor + consumer.LogsConsumer +} + +// ProcessorCapabilities describes the capabilities of a Processor. +type ProcessorCapabilities struct { + // MutatesConsumedData is set to true if Consume* function of the + // processor modifies the input TraceData or MetricsData argument. + // Processors which modify the input data MUST set this flag to true. If the processor + // does not modify the data it MUST set this flag to false. If the processor creates + // a copy of the data before modifying then this flag can be safely set to false. + MutatesConsumedData bool +} + +// ProcessorCreateParams is passed to Create* functions in ProcessorFactory. +type ProcessorCreateParams struct { + // Logger that the factory can use during creation and can pass to the created + // component to be used later as well. + Logger *zap.Logger + + // ApplicationStartInfo can be used by components for informational purposes + ApplicationStartInfo ApplicationStartInfo +} + +// ProcessorFactory is factory interface for processors. This is the +// new factory type that can create new style processors. +type ProcessorFactory interface { + Factory + + // CreateDefaultConfig creates the default configuration for the Processor. + // This method can be called multiple times depending on the pipeline + // configuration and should not cause side-effects that prevent the creation + // of multiple instances of the Processor. + // The object returned by this method needs to pass the checks implemented by + // 'configcheck.ValidateConfig'. It is recommended to have such check in the + // tests of any implementation of the Factory interface. + CreateDefaultConfig() configmodels.Processor + + // CreateTraceProcessor creates a trace processor based on this config. + // If the processor type does not support tracing or if the config is not valid + // error will be returned instead. + CreateTracesProcessor( + ctx context.Context, + params ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, + ) (TracesProcessor, error) + + // CreateMetricsProcessor creates a metrics processor based on this config. + // If the processor type does not support metrics or if the config is not valid + // error will be returned instead. + CreateMetricsProcessor( + ctx context.Context, + params ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.MetricsConsumer, + ) (MetricsProcessor, error) + + // CreateLogsProcessor creates a processor based on the config. + // If the processor type does not support logs or if the config is not valid + // error will be returned instead. + CreateLogsProcessor( + ctx context.Context, + params ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.LogsConsumer, + ) (LogsProcessor, error) +} diff --git a/internal/otel_collector/component/processor_test.go b/internal/otel_collector/component/processor_test.go new file mode 100644 index 00000000000..7849f1006e9 --- /dev/null +++ b/internal/otel_collector/component/processor_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +type TestProcessorFactory struct { + name string +} + +// Type gets the type of the Processor config created by this factory. +func (f *TestProcessorFactory) Type() configmodels.Type { + return configmodels.Type(f.name) +} + +// CreateDefaultConfig creates the default configuration for the Processor. +func (f *TestProcessorFactory) CreateDefaultConfig() configmodels.Processor { + return nil +} + +// CreateTraceProcessor creates a trace processor based on this config. +func (f *TestProcessorFactory) CreateTracesProcessor(context.Context, ProcessorCreateParams, configmodels.Processor, consumer.TracesConsumer) (TracesProcessor, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsProcessor creates a metrics processor based on this config. +func (f *TestProcessorFactory) CreateMetricsProcessor(context.Context, ProcessorCreateParams, configmodels.Processor, consumer.MetricsConsumer) (MetricsProcessor, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsProcessor creates a metrics processor based on this config. +func (f *TestProcessorFactory) CreateLogsProcessor(context.Context, ProcessorCreateParams, configmodels.Processor, consumer.LogsConsumer) (LogsProcessor, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +func TestFactoriesBuilder(t *testing.T) { + type testCase struct { + in []ProcessorFactory + out map[configmodels.Type]ProcessorFactory + } + + testCases := []testCase{ + { + in: []ProcessorFactory{ + &TestProcessorFactory{"p1"}, + &TestProcessorFactory{"p2"}, + }, + out: map[configmodels.Type]ProcessorFactory{ + "p1": &TestProcessorFactory{"p1"}, + "p2": &TestProcessorFactory{"p2"}, + }, + }, + { + in: []ProcessorFactory{ + &TestProcessorFactory{"p1"}, + &TestProcessorFactory{"p1"}, + }, + }, + } + + for _, c := range testCases { + out, err := MakeProcessorFactoryMap(c.in...) + if c.out == nil { + assert.Error(t, err) + continue + } + assert.NoError(t, err) + assert.Equal(t, c.out, out) + } +} diff --git a/internal/otel_collector/component/receiver.go b/internal/otel_collector/component/receiver.go new file mode 100644 index 00000000000..a0cd40dc889 --- /dev/null +++ b/internal/otel_collector/component/receiver.go @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +// Receiver defines functions that trace and metric receivers must implement. +type Receiver interface { + Component +} + +// A TracesReceiver is an "arbitrary data"-to-"internal format" converter. +// Its purpose is to translate data from the wild into internal trace format. +// TracesReceiver feeds a consumer.TracesConsumer with data. +// +// For example it could be Zipkin data source which translates +// Zipkin spans into consumerdata.TraceData. +type TracesReceiver interface { + Receiver +} + +// A MetricsReceiver is an "arbitrary data"-to-"internal format" converter. +// Its purpose is to translate data from the wild into internal metrics format. +// MetricsReceiver feeds a consumer.MetricsConsumer with data. +// +// For example it could be Prometheus data source which translates +// Prometheus metrics into consumerdata.MetricsData. +type MetricsReceiver interface { + Receiver +} + +// A LogsReceiver is a "log data"-to-"internal format" converter. +// Its purpose is to translate data from the wild into internal data format. +// LogsReceiver feeds a consumer.LogsConsumer with data. +type LogsReceiver interface { + Receiver +} + +// ReceiverCreateParams is passed to ReceiverFactory.Create* functions. +type ReceiverCreateParams struct { + // Logger that the factory can use during creation and can pass to the created + // component to be used later as well. + Logger *zap.Logger + + // ApplicationStartInfo can be used by components for informational purposes + ApplicationStartInfo ApplicationStartInfo +} + +// ReceiverFactory can create TracesReceiver and MetricsReceiver. This is the +// new factory type that can create new style receivers. +type ReceiverFactory interface { + Factory + + // CreateDefaultConfig creates the default configuration for the Receiver. + // This method can be called multiple times depending on the pipeline + // configuration and should not cause side-effects that prevent the creation + // of multiple instances of the Receiver. + // The object returned by this method needs to pass the checks implemented by + // 'configcheck.ValidateConfig'. It is recommended to have such check in the + // tests of any implementation of the Factory interface. + CreateDefaultConfig() configmodels.Receiver + + // CreateTraceReceiver creates a trace receiver based on this config. + // If the receiver type does not support tracing or if the config is not valid + // error will be returned instead. + CreateTracesReceiver(ctx context.Context, params ReceiverCreateParams, + cfg configmodels.Receiver, nextConsumer consumer.TracesConsumer) (TracesReceiver, error) + + // CreateMetricsReceiver creates a metrics receiver based on this config. + // If the receiver type does not support metrics or if the config is not valid + // error will be returned instead. + CreateMetricsReceiver(ctx context.Context, params ReceiverCreateParams, + cfg configmodels.Receiver, nextConsumer consumer.MetricsConsumer) (MetricsReceiver, error) + + // CreateLogsReceiver creates a log receiver based on this config. + // If the receiver type does not support the data type or if the config is not valid + // error will be returned instead. + CreateLogsReceiver(ctx context.Context, params ReceiverCreateParams, + cfg configmodels.Receiver, nextConsumer consumer.LogsConsumer) (LogsReceiver, error) +} diff --git a/internal/otel_collector/component/receiver_test.go b/internal/otel_collector/component/receiver_test.go new file mode 100644 index 00000000000..ee844afd4c2 --- /dev/null +++ b/internal/otel_collector/component/receiver_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +type TestReceiverFactory struct { + name configmodels.Type +} + +// Type gets the type of the Receiver config created by this factory. +func (f *TestReceiverFactory) Type() configmodels.Type { + return f.name +} + +// CreateDefaultConfig creates the default configuration for the Receiver. +func (f *TestReceiverFactory) CreateDefaultConfig() configmodels.Receiver { + return nil +} + +// CreateTraceReceiver creates a trace receiver based on this config. +func (f *TestReceiverFactory) CreateTracesReceiver(context.Context, ReceiverCreateParams, configmodels.Receiver, consumer.TracesConsumer) (TracesReceiver, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsReceiver creates a metrics receiver based on this config. +func (f *TestReceiverFactory) CreateMetricsReceiver(context.Context, ReceiverCreateParams, configmodels.Receiver, consumer.MetricsConsumer) (MetricsReceiver, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsReceiver creates a metrics receiver based on this config. +func (f *TestReceiverFactory) CreateLogsReceiver(context.Context, ReceiverCreateParams, configmodels.Receiver, consumer.LogsConsumer) (LogsReceiver, error) { + return nil, configerror.ErrDataTypeIsNotSupported +} + +func TestBuildReceivers(t *testing.T) { + type testCase struct { + in []ReceiverFactory + out map[configmodels.Type]ReceiverFactory + } + + testCases := []testCase{ + { + in: []ReceiverFactory{ + &TestReceiverFactory{"e1"}, + &TestReceiverFactory{"e2"}, + }, + out: map[configmodels.Type]ReceiverFactory{ + "e1": &TestReceiverFactory{"e1"}, + "e2": &TestReceiverFactory{"e2"}, + }, + }, + { + in: []ReceiverFactory{ + &TestReceiverFactory{"e1"}, + &TestReceiverFactory{"e1"}, + }, + }, + } + + for _, c := range testCases { + out, err := MakeReceiverFactoryMap(c.in...) + if c.out == nil { + assert.Error(t, err) + continue + } + assert.NoError(t, err) + assert.Equal(t, c.out, out) + } +} diff --git a/internal/otel_collector/config/config.go b/internal/otel_collector/config/config.go new file mode 100644 index 00000000000..5bbfcd0669c --- /dev/null +++ b/internal/otel_collector/config/config.go @@ -0,0 +1,780 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package config implements loading of configuration from Viper configuration. +// The implementation relies on registered factories that allow creating +// default configuration for each type of receiver/exporter/processor. +package config + +import ( + "errors" + "fmt" + "os" + "reflect" + "strings" + + "github.com/spf13/cast" + "github.com/spf13/viper" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" +) + +// These are errors that can be returned by Load(). Note that error codes are not part +// of Load()'s public API, they are for internal unit testing only. +type configErrorCode int + +const ( + _ configErrorCode = iota // skip 0, start errors codes from 1. + errInvalidSubConfig + errInvalidTypeAndNameKey + errUnknownType + errDuplicateName + errMissingPipelines + errPipelineMustHaveReceiver + errPipelineMustHaveExporter + errExtensionNotExists + errPipelineReceiverNotExists + errPipelineProcessorNotExists + errPipelineExporterNotExists + errMissingReceivers + errMissingExporters + errUnmarshalTopLevelStructureError +) + +const ( + // ViperDelimiter is used as the default key delimiter in the default viper instance + ViperDelimiter = "::" +) + +type configError struct { + msg string // human readable error message. + code configErrorCode // internal error code. +} + +func (e *configError) Error() string { + return e.msg +} + +// YAML top-level configuration keys +const ( + // extensionsKeyName is the configuration key name for extensions section. + extensionsKeyName = "extensions" + + // receiversKeyName is the configuration key name for receivers section. + receiversKeyName = "receivers" + + // exportersKeyName is the configuration key name for exporters section. + exportersKeyName = "exporters" + + // processorsKeyName is the configuration key name for processors section. + processorsKeyName = "processors" + + // pipelinesKeyName is the configuration key name for pipelines section. + pipelinesKeyName = "pipelines" +) + +type configSettings struct { + Receivers map[string]map[string]interface{} `mapstructure:"receivers"` + Processors map[string]map[string]interface{} `mapstructure:"processors"` + Exporters map[string]map[string]interface{} `mapstructure:"exporters"` + Extensions map[string]map[string]interface{} `mapstructure:"extensions"` + Service serviceSettings `mapstructure:"service"` +} + +type serviceSettings struct { + Extensions []string `mapstructure:"extensions"` + Pipelines map[string]pipelineSettings `mapstructure:"pipelines"` +} + +type pipelineSettings struct { + Receivers []string `mapstructure:"receivers"` + Processors []string `mapstructure:"processors"` + Exporters []string `mapstructure:"exporters"` +} + +// deprecatedUnmarshaler is the old/deprecated way to provide custom unmarshaler. +type deprecatedUnmarshaler interface { + // CustomUnmarshaler returns a custom unmarshaler for the configuration or nil if + // there is no need for custom unmarshaling. This is typically used if viper.UnmarshalExact() + // is not sufficient to unmarshal correctly. + CustomUnmarshaler() component.CustomUnmarshaler +} + +// typeAndNameSeparator is the separator that is used between type and name in type/name composite keys. +const typeAndNameSeparator = "/" + +// Creates a new Viper instance with a different key-delimitor "::" instead of the +// default ".". This way configs can have keys that contain ".". +func NewViper() *viper.Viper { + return viper.NewWithOptions(viper.KeyDelimiter(ViperDelimiter)) +} + +// Load loads a Config from Viper. +// After loading the config, need to check if it is valid by calling `ValidateConfig`. +func Load( + v *viper.Viper, + factories component.Factories, +) (*configmodels.Config, error) { + + var config configmodels.Config + + // Load the config. + + // Struct to validate top level sections. + var rawCfg configSettings + if err := v.UnmarshalExact(&rawCfg); err != nil { + return nil, &configError{ + code: errUnmarshalTopLevelStructureError, + msg: fmt.Sprintf("error reading top level configuration sections: %s", err.Error()), + } + } + + // In the following section use v.GetStringMap(xyzKeyName) instead of rawCfg.Xyz, because + // UnmarshalExact will not unmarshal entries in the map[string]interface{} with nil values. + // GetStringMap does the correct thing. + + // Start with the service extensions. + + extensions, err := loadExtensions(v.GetStringMap(extensionsKeyName), factories.Extensions) + if err != nil { + return nil, err + } + config.Extensions = extensions + + // Load data components (receivers, exporters, and processors). + + receivers, err := loadReceivers(v.GetStringMap(receiversKeyName), factories.Receivers) + if err != nil { + return nil, err + } + config.Receivers = receivers + + exporters, err := loadExporters(v.GetStringMap(exportersKeyName), factories.Exporters) + if err != nil { + return nil, err + } + config.Exporters = exporters + + processors, err := loadProcessors(v.GetStringMap(processorsKeyName), factories.Processors) + if err != nil { + return nil, err + } + config.Processors = processors + + // Load the service and its data pipelines. + service, err := loadService(rawCfg.Service) + if err != nil { + return nil, err + } + config.Service = service + + return &config, nil +} + +// DecodeTypeAndName decodes a key in type[/name] format into type and fullName. +// fullName is the key normalized such that type and name components have spaces trimmed. +// The "type" part must be present, the forward slash and "name" are optional. typeStr +// will be non-empty if err is nil. +func DecodeTypeAndName(key string) (typeStr configmodels.Type, fullName string, err error) { + items := strings.SplitN(key, typeAndNameSeparator, 2) + + if len(items) >= 1 { + typeStr = configmodels.Type(strings.TrimSpace(items[0])) + } + + if len(items) == 0 || typeStr == "" { + err = errors.New("type/name key must have the type part") + return + } + + var nameSuffix string + if len(items) > 1 { + // "name" part is present. + nameSuffix = strings.TrimSpace(items[1]) + if nameSuffix == "" { + err = errors.New("name part must be specified after " + typeAndNameSeparator + " in type/name key") + return + } + } else { + nameSuffix = "" + } + + // Create normalized fullName. + if nameSuffix == "" { + fullName = string(typeStr) + } else { + fullName = string(typeStr) + typeAndNameSeparator + nameSuffix + } + + err = nil + return +} + +func errorInvalidTypeAndNameKey(component, key string, err error) error { + return &configError{ + code: errInvalidTypeAndNameKey, + msg: fmt.Sprintf("invalid %s type and name key %q: %v", component, key, err), + } +} + +func errorUnknownType(component string, typeStr configmodels.Type, fullName string) error { + return &configError{ + code: errUnknownType, + msg: fmt.Sprintf("unknown %s type %q for %s", component, typeStr, fullName), + } +} + +func errorUnmarshalError(component string, fullName string, err error) error { + return &configError{ + code: errUnmarshalTopLevelStructureError, + msg: fmt.Sprintf("error reading %s configuration for %s: %v", component, fullName, err), + } +} + +func errorDuplicateName(component string, fullName string) error { + return &configError{ + code: errDuplicateName, + msg: fmt.Sprintf("duplicate %s name %s", component, fullName), + } +} + +func loadExtensions(exts map[string]interface{}, factories map[configmodels.Type]component.ExtensionFactory) (configmodels.Extensions, error) { + // Prepare resulting map. + extensions := make(configmodels.Extensions) + + // Iterate over extensions and create a config for each. + for key, value := range exts { + componentConfig := viperFromStringMap(cast.ToStringMap(value)) + expandEnvConfig(componentConfig) + + // Decode the key into type and fullName components. + typeStr, fullName, err := DecodeTypeAndName(key) + if err != nil { + return nil, errorInvalidTypeAndNameKey(extensionsKeyName, key, err) + } + + // Find extension factory based on "type" that we read from config source. + factory := factories[typeStr] + if factory == nil { + return nil, errorUnknownType(extensionsKeyName, typeStr, fullName) + } + + // Create the default config for this extension + extensionCfg := factory.CreateDefaultConfig() + extensionCfg.SetName(fullName) + expandEnvLoadedConfig(extensionCfg) + + // Now that the default config struct is created we can Unmarshal into it + // and it will apply user-defined config on top of the default. + unm := unmarshaler(factory) + if err := unm(componentConfig, extensionCfg); err != nil { + return nil, errorUnmarshalError(extensionsKeyName, fullName, err) + } + + if extensions[fullName] != nil { + return nil, errorDuplicateName(extensionsKeyName, fullName) + } + + extensions[fullName] = extensionCfg + } + + return extensions, nil +} + +func loadService(rawService serviceSettings) (configmodels.Service, error) { + var ret configmodels.Service + ret.Extensions = rawService.Extensions + + // Process the pipelines first so in case of error on them it can be properly + // reported. + pipelines, err := loadPipelines(rawService.Pipelines) + ret.Pipelines = pipelines + + return ret, err +} + +// LoadReceiver loads a receiver config from componentConfig using the provided factories. +func LoadReceiver(componentConfig *viper.Viper, typeStr configmodels.Type, fullName string, factory component.ReceiverFactory) (configmodels.Receiver, error) { + // Create the default config for this receiver. + receiverCfg := factory.CreateDefaultConfig() + receiverCfg.SetName(fullName) + expandEnvLoadedConfig(receiverCfg) + + // Now that the default config struct is created we can Unmarshal into it + // and it will apply user-defined config on top of the default. + unm := unmarshaler(factory) + if err := unm(componentConfig, receiverCfg); err != nil { + return nil, errorUnmarshalError(receiversKeyName, fullName, err) + } + + return receiverCfg, nil +} + +func loadReceivers(recvs map[string]interface{}, factories map[configmodels.Type]component.ReceiverFactory) (configmodels.Receivers, error) { + // Prepare resulting map + receivers := make(configmodels.Receivers) + + // Iterate over input map and create a config for each. + for key, value := range recvs { + componentConfig := viperFromStringMap(cast.ToStringMap(value)) + expandEnvConfig(componentConfig) + + // Decode the key into type and fullName components. + typeStr, fullName, err := DecodeTypeAndName(key) + if err != nil { + return nil, errorInvalidTypeAndNameKey(receiversKeyName, key, err) + } + + // Find receiver factory based on "type" that we read from config source + factory := factories[typeStr] + if factory == nil { + return nil, errorUnknownType(receiversKeyName, typeStr, fullName) + } + + receiverCfg, err := LoadReceiver(componentConfig, typeStr, fullName, factory) + + if err != nil { + // LoadReceiver already wraps the error. + return nil, err + } + + if receivers[receiverCfg.Name()] != nil { + return nil, errorDuplicateName(receiversKeyName, fullName) + } + receivers[receiverCfg.Name()] = receiverCfg + } + + return receivers, nil +} + +func loadExporters(exps map[string]interface{}, factories map[configmodels.Type]component.ExporterFactory) (configmodels.Exporters, error) { + // Prepare resulting map + exporters := make(configmodels.Exporters) + + // Iterate over Exporters and create a config for each. + for key, value := range exps { + componentConfig := viperFromStringMap(cast.ToStringMap(value)) + expandEnvConfig(componentConfig) + + // Decode the key into type and fullName components. + typeStr, fullName, err := DecodeTypeAndName(key) + if err != nil { + return nil, errorInvalidTypeAndNameKey(exportersKeyName, key, err) + } + + // Find exporter factory based on "type" that we read from config source + factory := factories[typeStr] + if factory == nil { + return nil, errorUnknownType(exportersKeyName, typeStr, fullName) + } + + // Create the default config for this exporter + exporterCfg := factory.CreateDefaultConfig() + exporterCfg.SetName(fullName) + expandEnvLoadedConfig(exporterCfg) + + // Now that the default config struct is created we can Unmarshal into it + // and it will apply user-defined config on top of the default. + unm := unmarshaler(factory) + if err := unm(componentConfig, exporterCfg); err != nil { + return nil, errorUnmarshalError(exportersKeyName, fullName, err) + } + + if exporters[fullName] != nil { + return nil, errorDuplicateName(exportersKeyName, fullName) + } + + exporters[fullName] = exporterCfg + } + + return exporters, nil +} + +func loadProcessors(procs map[string]interface{}, factories map[configmodels.Type]component.ProcessorFactory) (configmodels.Processors, error) { + // Prepare resulting map. + processors := make(configmodels.Processors) + + // Iterate over processors and create a config for each. + for key, value := range procs { + componentConfig := viperFromStringMap(cast.ToStringMap(value)) + expandEnvConfig(componentConfig) + + // Decode the key into type and fullName components. + typeStr, fullName, err := DecodeTypeAndName(key) + if err != nil { + return nil, errorInvalidTypeAndNameKey(processorsKeyName, key, err) + } + + // Find processor factory based on "type" that we read from config source. + factory := factories[typeStr] + if factory == nil { + return nil, errorUnknownType(processorsKeyName, typeStr, fullName) + } + + // Create the default config for this processor. + processorCfg := factory.CreateDefaultConfig() + processorCfg.SetName(fullName) + expandEnvLoadedConfig(processorCfg) + + // Now that the default config struct is created we can Unmarshal into it + // and it will apply user-defined config on top of the default. + unm := unmarshaler(factory) + if err := unm(componentConfig, processorCfg); err != nil { + return nil, errorUnmarshalError(processorsKeyName, fullName, err) + } + + if processors[fullName] != nil { + return nil, errorDuplicateName(processorsKeyName, fullName) + } + + processors[fullName] = processorCfg + } + + return processors, nil +} + +func loadPipelines(pipelinesConfig map[string]pipelineSettings) (configmodels.Pipelines, error) { + // Prepare resulting map. + pipelines := make(configmodels.Pipelines) + + // Iterate over input map and create a config for each. + for key, rawPipeline := range pipelinesConfig { + // Decode the key into type and name components. + typeStr, fullName, err := DecodeTypeAndName(key) + if err != nil { + return nil, errorInvalidTypeAndNameKey(pipelinesKeyName, key, err) + } + + // Create the config for this pipeline. + var pipelineCfg configmodels.Pipeline + + // Set the type. + pipelineCfg.InputType = configmodels.DataType(typeStr) + switch pipelineCfg.InputType { + case configmodels.TracesDataType: + case configmodels.MetricsDataType: + case configmodels.LogsDataType: + default: + return nil, errorUnknownType(pipelinesKeyName, typeStr, fullName) + } + + pipelineCfg.Name = fullName + pipelineCfg.Receivers = rawPipeline.Receivers + pipelineCfg.Processors = rawPipeline.Processors + pipelineCfg.Exporters = rawPipeline.Exporters + + if pipelines[fullName] != nil { + return nil, errorDuplicateName(pipelinesKeyName, fullName) + } + + pipelines[fullName] = &pipelineCfg + } + + return pipelines, nil +} + +// ValidateConfig validates config. +func ValidateConfig(cfg *configmodels.Config, _ *zap.Logger) error { + // This function performs basic validation of configuration. There may be more subtle + // invalid cases that we currently don't check for but which we may want to add in + // the future (e.g. disallowing receiving and exporting on the same endpoint). + + if err := validateReceivers(cfg); err != nil { + return err + } + + if err := validateExporters(cfg); err != nil { + return err + } + + if err := validateService(cfg); err != nil { + return err + } + + return nil +} + +func validateService(cfg *configmodels.Config) error { + if err := validatePipelines(cfg); err != nil { + return err + } + + return validateServiceExtensions(cfg) +} + +func validateServiceExtensions(cfg *configmodels.Config) error { + if len(cfg.Service.Extensions) == 0 { + return nil + } + + // Validate extensions. + for _, ref := range cfg.Service.Extensions { + // Check that the name referenced in the Service extensions exists in the top-level extensions + if cfg.Extensions[ref] == nil { + return &configError{ + code: errExtensionNotExists, + msg: fmt.Sprintf("Service references extension %q which does not exist", ref), + } + } + } + + return nil +} + +func validatePipelines(cfg *configmodels.Config) error { + // Must have at least one pipeline. + if len(cfg.Service.Pipelines) == 0 { + return &configError{code: errMissingPipelines, msg: "must have at least one pipeline"} + } + + // Validate pipelines. + for _, pipeline := range cfg.Service.Pipelines { + if err := validatePipeline(cfg, pipeline); err != nil { + return err + } + } + return nil +} + +func validatePipeline(cfg *configmodels.Config, pipeline *configmodels.Pipeline) error { + if err := validatePipelineReceivers(cfg, pipeline); err != nil { + return err + } + + if err := validatePipelineExporters(cfg, pipeline); err != nil { + return err + } + + if err := validatePipelineProcessors(cfg, pipeline); err != nil { + return err + } + + return nil +} + +func validatePipelineReceivers(cfg *configmodels.Config, pipeline *configmodels.Pipeline) error { + if len(pipeline.Receivers) == 0 { + return &configError{ + code: errPipelineMustHaveReceiver, + msg: fmt.Sprintf("pipeline %q must have at least one receiver", pipeline.Name), + } + } + + // Validate pipeline receiver name references. + for _, ref := range pipeline.Receivers { + // Check that the name referenced in the pipeline's receivers exists in the top-level receivers + if cfg.Receivers[ref] == nil { + return &configError{ + code: errPipelineReceiverNotExists, + msg: fmt.Sprintf("pipeline %q references receiver %q which does not exist", pipeline.Name, ref), + } + } + } + + return nil +} + +func validatePipelineExporters(cfg *configmodels.Config, pipeline *configmodels.Pipeline) error { + if len(pipeline.Exporters) == 0 { + return &configError{ + code: errPipelineMustHaveExporter, + msg: fmt.Sprintf("pipeline %q must have at least one exporter", pipeline.Name), + } + } + + // Validate pipeline exporter name references. + for _, ref := range pipeline.Exporters { + // Check that the name referenced in the pipeline's Exporters exists in the top-level Exporters + if cfg.Exporters[ref] == nil { + return &configError{ + code: errPipelineExporterNotExists, + msg: fmt.Sprintf("pipeline %q references exporter %q which does not exist", pipeline.Name, ref), + } + } + } + + return nil +} + +func validatePipelineProcessors(cfg *configmodels.Config, pipeline *configmodels.Pipeline) error { + if len(pipeline.Processors) == 0 { + return nil + } + + // Validate pipeline processor name references + for _, ref := range pipeline.Processors { + // Check that the name referenced in the pipeline's processors exists in the top-level processors. + if cfg.Processors[ref] == nil { + return &configError{ + code: errPipelineProcessorNotExists, + msg: fmt.Sprintf("pipeline %q references processor %s which does not exist", pipeline.Name, ref), + } + } + } + + return nil +} + +func validateReceivers(cfg *configmodels.Config) error { + // Currently there is no default receiver enabled. The configuration must specify at least one enabled receiver to + // be valid. + if len(cfg.Receivers) == 0 { + return &configError{ + code: errMissingReceivers, + msg: "no enabled receivers specified in config", + } + } + return nil +} + +func validateExporters(cfg *configmodels.Config) error { + // There must be at least one enabled exporter to be considered a valid configuration. + if len(cfg.Exporters) == 0 { + return &configError{ + code: errMissingExporters, + msg: "no enabled exporters specified in config", + } + } + return nil +} + +// expandEnvConfig creates a new viper config with expanded values for all the values (simple, list or map value). +// It does not expand the keys. +func expandEnvConfig(v *viper.Viper) { + for _, k := range v.AllKeys() { + v.Set(k, expandStringValues(v.Get(k))) + } +} + +func expandStringValues(value interface{}) interface{} { + switch v := value.(type) { + default: + return v + case string: + return expandEnv(v) + case []interface{}: + nslice := make([]interface{}, 0, len(v)) + for _, vint := range v { + nslice = append(nslice, expandStringValues(vint)) + } + return nslice + case map[interface{}]interface{}: + nmap := make(map[interface{}]interface{}, len(v)) + for k, vint := range v { + nmap[k] = expandStringValues(vint) + } + return nmap + } +} + +// expandEnvLoadedConfig is a utility function that goes recursively through a config object +// and tries to expand environment variables in its string fields. +func expandEnvLoadedConfig(s interface{}) { + expandEnvLoadedConfigPointer(s) +} + +func expandEnvLoadedConfigPointer(s interface{}) { + // Check that the value given is indeed a pointer, otherwise safely stop the search here + value := reflect.ValueOf(s) + if value.Kind() != reflect.Ptr { + return + } + // Run expandLoadedConfigValue on the value behind the pointer + expandEnvLoadedConfigValue(value.Elem()) +} + +func expandEnvLoadedConfigValue(value reflect.Value) { + // The value given is a string, we expand it (if allowed) + if value.Kind() == reflect.String && value.CanSet() { + value.SetString(expandEnv(value.String())) + } + // The value given is a struct, we go through its fields + if value.Kind() == reflect.Struct { + for i := 0; i < value.NumField(); i++ { + field := value.Field(i) // Returns the content of the field + if field.CanSet() { // Only try to modify a field if it can be modified (eg. skip unexported private fields) + switch field.Kind() { + case reflect.String: // The current field is a string, we want to expand it + field.SetString(expandEnv(field.String())) // Expand env variables in the string + case reflect.Ptr: // The current field is a pointer + expandEnvLoadedConfigPointer(field.Interface()) // Run the expansion function on the pointer + case reflect.Struct: // The current field is a nested struct + expandEnvLoadedConfigValue(field) // Go through the nested struct + } + } + } + } +} + +func expandEnv(s string) string { + return os.Expand(s, func(str string) string { + // This allows escaping environment variable substitution via $$, e.g. + // - $FOO will be substituted with env var FOO + // - $$FOO will be replaced with $FOO + // - $$$FOO will be replaced with $ + substituted env var FOO + if str == "$" { + return "$" + } + return os.Getenv(str) + }) +} + +func unmarshaler(factory component.Factory) component.CustomUnmarshaler { + if fu, ok := factory.(component.ConfigUnmarshaler); ok { + return fu.Unmarshal + } + + if du, ok := factory.(deprecatedUnmarshaler); ok { + cu := du.CustomUnmarshaler() + if cu != nil { + return cu + } + } + + return defaultUnmarshaler +} + +func defaultUnmarshaler(componentViperSection *viper.Viper, intoCfg interface{}) error { + return componentViperSection.UnmarshalExact(intoCfg) +} + +// Copied from the Viper but changed to use the same delimiter +// and return error if the sub is not a map. +// See https://github.com/spf13/viper/issues/871 +func ViperSubExact(v *viper.Viper, key string) (*viper.Viper, error) { + data := v.Get(key) + if data == nil { + return NewViper(), nil + } + + if reflect.TypeOf(data).Kind() == reflect.Map { + subv := NewViper() + // Cannot return error because the subv is empty. + _ = subv.MergeConfigMap(cast.ToStringMap(data)) + return subv, nil + } + return nil, &configError{ + code: errInvalidSubConfig, + msg: fmt.Sprintf("unexpected sub-config value kind for key:%s value:%v kind:%v)", key, data, reflect.TypeOf(data).Kind()), + } +} + +func viperFromStringMap(data map[string]interface{}) *viper.Viper { + v := NewViper() + // Cannot return error because the subv is empty. + _ = v.MergeConfigMap(cast.ToStringMap(data)) + return v +} diff --git a/internal/otel_collector/config/config_test.go b/internal/otel_collector/config/config_test.go new file mode 100644 index 00000000000..e0ed1589c90 --- /dev/null +++ b/internal/otel_collector/config/config_test.go @@ -0,0 +1,797 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" +) + +func TestDecodeConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + // Load the config + config, err := loadConfigFile(t, path.Join(".", "testdata", "valid-config.yaml"), factories) + require.NoError(t, err, "Unable to load config") + + // Verify extensions. + assert.Equal(t, 3, len(config.Extensions)) + assert.Equal(t, "some string", config.Extensions["exampleextension/1"].(*componenttest.ExampleExtensionCfg).ExtraSetting) + + // Verify service. + assert.Equal(t, 2, len(config.Service.Extensions)) + assert.Equal(t, "exampleextension/0", config.Service.Extensions[0]) + assert.Equal(t, "exampleextension/1", config.Service.Extensions[1]) + + // Verify receivers + assert.Equal(t, 2, len(config.Receivers), "Incorrect receivers count") + + assert.Equal(t, + &componenttest.ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "examplereceiver", + NameVal: "examplereceiver", + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:1000", + }, + ExtraSetting: "some string", + }, + config.Receivers["examplereceiver"], + "Did not load receiver config correctly") + + assert.Equal(t, + &componenttest.ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "examplereceiver", + NameVal: "examplereceiver/myreceiver", + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:12345", + }, + ExtraSetting: "some string", + }, + config.Receivers["examplereceiver/myreceiver"], + "Did not load receiver config correctly") + + // Verify exporters + assert.Equal(t, 2, len(config.Exporters), "Incorrect exporters count") + + assert.Equal(t, + &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter", + TypeVal: "exampleexporter", + }, + ExtraSetting: "some export string", + }, + config.Exporters["exampleexporter"], + "Did not load exporter config correctly") + + assert.Equal(t, + &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter/myexporter", + TypeVal: "exampleexporter", + }, + ExtraSetting: "some export string 2", + }, + config.Exporters["exampleexporter/myexporter"], + "Did not load exporter config correctly") + + // Verify Processors + assert.Equal(t, 1, len(config.Processors), "Incorrect processors count") + + assert.Equal(t, + &componenttest.ExampleProcessorCfg{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "exampleprocessor", + NameVal: "exampleprocessor", + }, + ExtraSetting: "some export string", + }, + config.Processors["exampleprocessor"], + "Did not load processor config correctly") + + // Verify Pipelines + assert.Equal(t, 1, len(config.Service.Pipelines), "Incorrect pipelines count") + + assert.Equal(t, + &configmodels.Pipeline{ + Name: "traces", + InputType: configmodels.TracesDataType, + Receivers: []string{"examplereceiver"}, + Processors: []string{"exampleprocessor"}, + Exporters: []string{"exampleexporter"}, + }, + config.Service.Pipelines["traces"], + "Did not load pipeline config correctly") +} + +func TestSimpleConfig(t *testing.T) { + var testCases = []struct { + name string // test case name (also file name containing config yaml) + }{ + {name: "simple-config-with-no-env"}, + {name: "simple-config-with-partial-env"}, + {name: "simple-config-with-all-env"}, + } + + const extensionExtra = "some extension string" + const extensionExtraMapValue = "some extension map value" + const extensionExtraListElement = "some extension list value" + assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA", extensionExtra)) + assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1", extensionExtraMapValue+"_1")) + assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2", extensionExtraMapValue+"_2")) + assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1", extensionExtraListElement+"_1")) + assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_2", extensionExtraListElement+"_2")) + + const receiverExtra = "some receiver string" + const receiverExtraMapValue = "some receiver map value" + const receiverExtraListElement = "some receiver list value" + assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA", receiverExtra)) + assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_1", receiverExtraMapValue+"_1")) + assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2", receiverExtraMapValue+"_2")) + assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1", receiverExtraListElement+"_1")) + assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_2", receiverExtraListElement+"_2")) + + const processorExtra = "some processor string" + const processorExtraMapValue = "some processor map value" + const processorExtraListElement = "some processor list value" + assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA", processorExtra)) + assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1", processorExtraMapValue+"_1")) + assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_2", processorExtraMapValue+"_2")) + assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1", processorExtraListElement+"_1")) + assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_2", processorExtraListElement+"_2")) + + const exporterExtra = "some exporter string" + const exporterExtraMapValue = "some exporter map value" + const exporterExtraListElement = "some exporter list value" + assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_INT", "65")) + assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA", exporterExtra)) + assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_1", exporterExtraMapValue+"_1")) + assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_2", exporterExtraMapValue+"_2")) + assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1", exporterExtraListElement+"_1")) + assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2", exporterExtraListElement+"_2")) + + defer func() { + assert.NoError(t, os.Unsetenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA")) + assert.NoError(t, os.Unsetenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE")) + assert.NoError(t, os.Unsetenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1")) + + assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA")) + assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE")) + assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1")) + + assert.NoError(t, os.Unsetenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA")) + assert.NoError(t, os.Unsetenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE")) + assert.NoError(t, os.Unsetenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1")) + + assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_INT")) + assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA")) + assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE")) + assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1")) + }() + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + // Load the config + config, err := loadConfigFile(t, path.Join(".", "testdata", test.name+".yaml"), factories) + require.NoError(t, err, "Unable to load config") + + // Verify extensions. + assert.Equalf(t, 1, len(config.Extensions), "TEST[%s]", test.name) + assert.Equalf(t, + &componenttest.ExampleExtensionCfg{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "exampleextension", + NameVal: "exampleextension", + }, + ExtraSetting: extensionExtra, + ExtraMapSetting: map[string]string{"ext-1": extensionExtraMapValue + "_1", "ext-2": extensionExtraMapValue + "_2"}, + ExtraListSetting: []string{extensionExtraListElement + "_1", extensionExtraListElement + "_2"}, + }, + config.Extensions["exampleextension"], + "TEST[%s] Did not load extension config correctly", test.name) + + // Verify service. + assert.Equalf(t, 1, len(config.Service.Extensions), "TEST[%s]", test.name) + assert.Equalf(t, "exampleextension", config.Service.Extensions[0], "TEST[%s]", test.name) + + // Verify receivers + assert.Equalf(t, 1, len(config.Receivers), "TEST[%s]", test.name) + + assert.Equalf(t, + &componenttest.ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "examplereceiver", + NameVal: "examplereceiver", + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:1234", + }, + ExtraSetting: receiverExtra, + ExtraMapSetting: map[string]string{"recv.1": receiverExtraMapValue + "_1", "recv.2": receiverExtraMapValue + "_2"}, + ExtraListSetting: []string{receiverExtraListElement + "_1", receiverExtraListElement + "_2"}, + }, + config.Receivers["examplereceiver"], + "TEST[%s] Did not load receiver config correctly", test.name) + + // Verify exporters + assert.Equalf(t, 1, len(config.Exporters), "TEST[%s]", test.name) + + assert.Equalf(t, + &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter", + TypeVal: "exampleexporter", + }, + ExtraInt: 65, + ExtraSetting: exporterExtra, + ExtraMapSetting: map[string]string{"exp_1": exporterExtraMapValue + "_1", "exp_2": exporterExtraMapValue + "_2"}, + ExtraListSetting: []string{exporterExtraListElement + "_1", exporterExtraListElement + "_2"}, + }, + config.Exporters["exampleexporter"], + "TEST[%s] Did not load exporter config correctly", test.name) + + // Verify Processors + assert.Equalf(t, 1, len(config.Processors), "TEST[%s]", test.name) + + assert.Equalf(t, + &componenttest.ExampleProcessorCfg{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "exampleprocessor", + NameVal: "exampleprocessor", + }, + ExtraSetting: processorExtra, + ExtraMapSetting: map[string]string{"proc_1": processorExtraMapValue + "_1", "proc_2": processorExtraMapValue + "_2"}, + ExtraListSetting: []string{processorExtraListElement + "_1", processorExtraListElement + "_2"}, + }, + config.Processors["exampleprocessor"], + "TEST[%s] Did not load processor config correctly", test.name) + + // Verify Pipelines + assert.Equalf(t, 1, len(config.Service.Pipelines), "TEST[%s]", test.name) + + assert.Equalf(t, + &configmodels.Pipeline{ + Name: "traces", + InputType: configmodels.TracesDataType, + Receivers: []string{"examplereceiver"}, + Processors: []string{"exampleprocessor"}, + Exporters: []string{"exampleexporter"}, + }, + config.Service.Pipelines["traces"], + "TEST[%s] Did not load pipeline config correctly", test.name) + }) + } +} + +func TestEscapedEnvVars(t *testing.T) { + const receiverExtraMapValue = "some receiver map value" + assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2", receiverExtraMapValue)) + defer func() { + assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2")) + }() + + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + // Load the config + config, err := loadConfigFile(t, path.Join(".", "testdata", "simple-config-with-escaped-env.yaml"), factories) + require.NoError(t, err, "Unable to load config") + + // Verify extensions. + assert.Equal(t, 1, len(config.Extensions)) + assert.Equal(t, + &componenttest.ExampleExtensionCfg{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "exampleextension", + NameVal: "exampleextension", + }, + ExtraSetting: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA}", + ExtraMapSetting: map[string]string{"ext-1": "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1}", "ext-2": "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2}"}, + ExtraListSetting: []string{"${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1}", "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_2}"}, + }, + config.Extensions["exampleextension"], + "Did not load extension config correctly") + + // Verify service. + assert.Equal(t, 1, len(config.Service.Extensions)) + assert.Equal(t, "exampleextension", config.Service.Extensions[0]) + + // Verify receivers + assert.Equal(t, 1, len(config.Receivers)) + + assert.Equal(t, + &componenttest.ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "examplereceiver", + NameVal: "examplereceiver", + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:1234", + }, + ExtraSetting: "$RECEIVERS_EXAMPLERECEIVER_EXTRA", + ExtraMapSetting: map[string]string{ + // $$ -> escaped $ + "recv.1": "$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_1", + // $$$ -> escaped $ + substituted env var + "recv.2": "$" + receiverExtraMapValue, + // $$$$ -> two escaped $ + "recv.3": "$$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_3", + // escaped $ in the middle + "recv.4": "some${RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_4}text", + // $$$$ -> two escaped $ + "recv.5": "${ONE}${TWO}", + // trailing escaped $ + "recv.6": "text$", + // escaped $ alone + "recv.7": "$", + }, + ExtraListSetting: []string{"$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1", "$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_2"}, + }, + config.Receivers["examplereceiver"], + "Did not load receiver config correctly") + + // Verify exporters + assert.Equal(t, 1, len(config.Exporters)) + + assert.Equal(t, + &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter", + TypeVal: "exampleexporter", + }, + ExtraSetting: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA}", + ExtraMapSetting: map[string]string{"exp_1": "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_1}", "exp_2": "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_2}"}, + ExtraListSetting: []string{"${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1}", "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2}"}, + }, + config.Exporters["exampleexporter"], + "Did not load exporter config correctly") + + // Verify Processors + assert.Equal(t, 1, len(config.Processors)) + + assert.Equal(t, + &componenttest.ExampleProcessorCfg{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "exampleprocessor", + NameVal: "exampleprocessor", + }, + ExtraSetting: "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA", + ExtraMapSetting: map[string]string{"proc_1": "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1", "proc_2": "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_2"}, + ExtraListSetting: []string{"$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1", "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_2"}, + }, + config.Processors["exampleprocessor"], + "Did not load processor config correctly") + + // Verify Pipelines + assert.Equal(t, 1, len(config.Service.Pipelines)) + + assert.Equal(t, + &configmodels.Pipeline{ + Name: "traces", + InputType: configmodels.TracesDataType, + Receivers: []string{"examplereceiver"}, + Processors: []string{"exampleprocessor"}, + Exporters: []string{"exampleexporter"}, + }, + config.Service.Pipelines["traces"], + "Did not load pipeline config correctly") +} + +func TestDecodeConfig_MultiProto(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + // Load the config + config, err := loadConfigFile(t, path.Join(".", "testdata", "multiproto-config.yaml"), factories) + require.NoError(t, err, "Unable to load config") + + // Verify receivers + assert.Equal(t, 2, len(config.Receivers), "Incorrect receivers count") + + assert.Equal(t, + &componenttest.MultiProtoReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "multireceiver", + NameVal: "multireceiver", + }, + Protocols: map[string]componenttest.MultiProtoReceiverOneCfg{ + "http": { + Endpoint: "example.com:8888", + ExtraSetting: "extra string 1", + }, + "tcp": { + Endpoint: "omnition.com:9999", + ExtraSetting: "extra string 2", + }, + }, + }, + config.Receivers["multireceiver"], + "Did not load receiver config correctly") + + assert.Equal(t, + &componenttest.MultiProtoReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "multireceiver", + NameVal: "multireceiver/myreceiver", + }, + Protocols: map[string]componenttest.MultiProtoReceiverOneCfg{ + "http": { + Endpoint: "localhost:12345", + ExtraSetting: "some string 1", + }, + "tcp": { + Endpoint: "0.0.0.0:4567", + ExtraSetting: "some string 2", + }, + }, + }, + config.Receivers["multireceiver/myreceiver"], + "Did not load receiver config correctly") +} + +func TestDecodeConfig_Invalid(t *testing.T) { + + var testCases = []struct { + name string // test case name (also file name containing config yaml) + expected configErrorCode // expected error (if nil any error is acceptable) + expectedMessage string // string that the error must contain + }{ + {name: "empty-config"}, + {name: "missing-all-sections"}, + {name: "missing-exporters", expected: errMissingExporters}, + {name: "missing-receivers", expected: errMissingReceivers}, + {name: "missing-processors"}, + {name: "invalid-extension-name", expected: errExtensionNotExists}, + {name: "invalid-receiver-name"}, + {name: "invalid-receiver-reference", expected: errPipelineReceiverNotExists}, + {name: "missing-extension-type", expected: errInvalidTypeAndNameKey}, + {name: "missing-receiver-type", expected: errInvalidTypeAndNameKey}, + {name: "missing-exporter-name-after-slash", expected: errInvalidTypeAndNameKey}, + {name: "missing-processor-type", expected: errInvalidTypeAndNameKey}, + {name: "missing-pipelines", expected: errMissingPipelines}, + {name: "pipeline-must-have-exporter", expected: errPipelineMustHaveExporter}, + {name: "pipeline-must-have-exporter2", expected: errPipelineMustHaveExporter}, + {name: "pipeline-must-have-receiver", expected: errPipelineMustHaveReceiver}, + {name: "pipeline-must-have-receiver2", expected: errPipelineMustHaveReceiver}, + {name: "pipeline-exporter-not-exists", expected: errPipelineExporterNotExists}, + {name: "pipeline-processor-not-exists", expected: errPipelineProcessorNotExists}, + {name: "unknown-extension-type", expected: errUnknownType, expectedMessage: "extensions"}, + {name: "unknown-receiver-type", expected: errUnknownType, expectedMessage: "receivers"}, + {name: "unknown-exporter-type", expected: errUnknownType, expectedMessage: "exporters"}, + {name: "unknown-processor-type", expected: errUnknownType, expectedMessage: "processors"}, + {name: "invalid-service-extensions-value", expected: errUnmarshalTopLevelStructureError, expectedMessage: "service"}, + {name: "invalid-sequence-value", expected: errUnmarshalTopLevelStructureError, expectedMessage: "pipelines"}, + {name: "invalid-pipeline-type", expected: errUnknownType, expectedMessage: "pipelines"}, + {name: "invalid-pipeline-type-and-name", expected: errInvalidTypeAndNameKey}, + {name: "duplicate-extension", expected: errDuplicateName, expectedMessage: "extensions"}, + {name: "duplicate-receiver", expected: errDuplicateName, expectedMessage: "receivers"}, + {name: "duplicate-exporter", expected: errDuplicateName, expectedMessage: "exporters"}, + {name: "duplicate-processor", expected: errDuplicateName, expectedMessage: "processors"}, + {name: "duplicate-pipeline", expected: errDuplicateName, expectedMessage: "pipelines"}, + {name: "invalid-top-level-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "top level"}, + {name: "invalid-extension-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "extensions"}, + {name: "invalid-service-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "service"}, + {name: "invalid-receiver-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "receivers"}, + {name: "invalid-processor-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "processors"}, + {name: "invalid-exporter-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "exporters"}, + {name: "invalid-pipeline-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "pipelines"}, + {name: "invalid-extension-sub-config", expected: errUnmarshalTopLevelStructureError}, + {name: "invalid-exporter-sub-config", expected: errUnmarshalTopLevelStructureError}, + {name: "invalid-processor-sub-config", expected: errUnmarshalTopLevelStructureError}, + {name: "invalid-receiver-sub-config", expected: errUnmarshalTopLevelStructureError}, + {name: "invalid-pipeline-sub-config", expected: errUnmarshalTopLevelStructureError}, + } + + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + _, err := loadConfigFile(t, path.Join(".", "testdata", test.name+".yaml"), factories) + if err == nil { + t.Error("expected error but succeeded") + } else if test.expected != 0 { + cfgErr, ok := err.(*configError) + if !ok { + t.Errorf("expected config error code %v but got a different error '%v'", test.expected, err) + } else { + assert.Equal(t, test.expected, cfgErr.code, err) + if test.expectedMessage != "" { + assert.Contains(t, cfgErr.Error(), test.expectedMessage) + } + assert.NotEmpty(t, cfgErr.Error(), "returned config error %v with empty error message", cfgErr.code) + } + } + }) + } +} + +func TestLoadEmptyConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + // Open the file for reading. + file, err := os.Open(path.Join(".", "testdata", "empty-config.yaml")) + require.NoError(t, err) + + defer func() { + require.NoError(t, file.Close()) + }() + + // Read yaml config from file + v := NewViper() + v.SetConfigType("yaml") + require.NoError(t, v.ReadConfig(file)) + + // Load the config from viper using the given factories. + _, err = Load(v, factories) + assert.NoError(t, err) +} + +func loadConfigFile(t *testing.T, fileName string, factories component.Factories) (*configmodels.Config, error) { + // Read yaml config from file + v := NewViper() + v.SetConfigFile(fileName) + require.NoErrorf(t, v.ReadInConfig(), "unable to read the file %v", fileName) + + // Load the config from viper using the given factories. + cfg, err := Load(v, factories) + if err != nil { + return nil, err + } + return cfg, ValidateConfig(cfg, zap.NewNop()) +} + +type nestedConfig struct { + NestedStringValue string + NestedIntValue int +} + +type testConfig struct { + configmodels.ExporterSettings + + NestedConfigPtr *nestedConfig + NestedConfigValue nestedConfig + StringValue string + StringPtrValue *string + IntValue int +} + +func TestExpandEnvLoadedConfig(t *testing.T) { + assert.NoError(t, os.Setenv("NESTED_VALUE", "replaced_nested_value")) + assert.NoError(t, os.Setenv("VALUE", "replaced_value")) + assert.NoError(t, os.Setenv("PTR_VALUE", "replaced_ptr_value")) + + defer func() { + assert.NoError(t, os.Unsetenv("NESTED_VALUE")) + assert.NoError(t, os.Unsetenv("VALUE")) + assert.NoError(t, os.Unsetenv("PTR_VALUE")) + }() + + testString := "$PTR_VALUE" + + config := &testConfig{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: configmodels.Type("test"), + NameVal: "test", + }, + NestedConfigPtr: &nestedConfig{ + NestedStringValue: "$NESTED_VALUE", + NestedIntValue: 1, + }, + NestedConfigValue: nestedConfig{ + NestedStringValue: "$NESTED_VALUE", + NestedIntValue: 2, + }, + StringValue: "$VALUE", + StringPtrValue: &testString, + IntValue: 3, + } + + expandEnvLoadedConfig(config) + + replacedTestString := "replaced_ptr_value" + + assert.Equal(t, &testConfig{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: configmodels.Type("test"), + NameVal: "test", + }, + NestedConfigPtr: &nestedConfig{ + NestedStringValue: "replaced_nested_value", + NestedIntValue: 1, + }, + NestedConfigValue: nestedConfig{ + NestedStringValue: "replaced_nested_value", + NestedIntValue: 2, + }, + StringValue: "replaced_value", + StringPtrValue: &replacedTestString, + IntValue: 3, + }, config) +} + +func TestExpandEnvLoadedConfigEscapedEnv(t *testing.T) { + assert.NoError(t, os.Setenv("NESTED_VALUE", "replaced_nested_value")) + assert.NoError(t, os.Setenv("ESCAPED_VALUE", "replaced_escaped_value")) + assert.NoError(t, os.Setenv("ESCAPED_PTR_VALUE", "replaced_escaped_pointer_value")) + + defer func() { + assert.NoError(t, os.Unsetenv("NESTED_VALUE")) + assert.NoError(t, os.Unsetenv("ESCAPED_VALUE")) + assert.NoError(t, os.Unsetenv("ESCAPED_PTR_VALUE")) + }() + + testString := "$$ESCAPED_PTR_VALUE" + + config := &testConfig{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: configmodels.Type("test"), + NameVal: "test", + }, + NestedConfigPtr: &nestedConfig{ + NestedStringValue: "$NESTED_VALUE", + NestedIntValue: 1, + }, + NestedConfigValue: nestedConfig{ + NestedStringValue: "$NESTED_VALUE", + NestedIntValue: 2, + }, + StringValue: "$$ESCAPED_VALUE", + StringPtrValue: &testString, + IntValue: 3, + } + + expandEnvLoadedConfig(config) + + replacedTestString := "$ESCAPED_PTR_VALUE" + + assert.Equal(t, &testConfig{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: configmodels.Type("test"), + NameVal: "test", + }, + NestedConfigPtr: &nestedConfig{ + NestedStringValue: "replaced_nested_value", + NestedIntValue: 1, + }, + NestedConfigValue: nestedConfig{ + NestedStringValue: "replaced_nested_value", + NestedIntValue: 2, + }, + StringValue: "$ESCAPED_VALUE", + StringPtrValue: &replacedTestString, + IntValue: 3, + }, config) +} + +func TestExpandEnvLoadedConfigMissingEnv(t *testing.T) { + assert.NoError(t, os.Setenv("NESTED_VALUE", "replaced_nested_value")) + + defer func() { + assert.NoError(t, os.Unsetenv("NESTED_VALUE")) + }() + + testString := "$PTR_VALUE" + + config := &testConfig{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: configmodels.Type("test"), + NameVal: "test", + }, + NestedConfigPtr: &nestedConfig{ + NestedStringValue: "$NESTED_VALUE", + NestedIntValue: 1, + }, + NestedConfigValue: nestedConfig{ + NestedStringValue: "$NESTED_VALUE", + NestedIntValue: 2, + }, + StringValue: "$VALUE", + StringPtrValue: &testString, + IntValue: 3, + } + + expandEnvLoadedConfig(config) + + replacedTestString := "" + + assert.Equal(t, &testConfig{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: configmodels.Type("test"), + NameVal: "test", + }, + NestedConfigPtr: &nestedConfig{ + NestedStringValue: "replaced_nested_value", + NestedIntValue: 1, + }, + NestedConfigValue: nestedConfig{ + NestedStringValue: "replaced_nested_value", + NestedIntValue: 2, + }, + StringValue: "", + StringPtrValue: &replacedTestString, + IntValue: 3, + }, config) +} + +func TestExpandEnvLoadedConfigNil(t *testing.T) { + var config *testConfig + + // This should safely do nothing + expandEnvLoadedConfig(config) + + assert.Equal(t, (*testConfig)(nil), config) +} + +func TestExpandEnvLoadedConfigNoPointer(t *testing.T) { + assert.NoError(t, os.Setenv("VALUE", "replaced_value")) + + config := testConfig{ + StringValue: "$VALUE", + } + + // This should do nothing as config is not a pointer + expandEnvLoadedConfig(config) + + assert.Equal(t, testConfig{ + StringValue: "$VALUE", + }, config) +} + +type testUnexportedConfig struct { + configmodels.ExporterSettings + + unexportedStringValue string + ExportedStringValue string +} + +func TestExpandEnvLoadedConfigUnexportedField(t *testing.T) { + assert.NoError(t, os.Setenv("VALUE", "replaced_value")) + + defer func() { + assert.NoError(t, os.Unsetenv("VALUE")) + }() + + config := &testUnexportedConfig{ + unexportedStringValue: "$VALUE", + ExportedStringValue: "$VALUE", + } + + expandEnvLoadedConfig(config) + + assert.Equal(t, &testUnexportedConfig{ + unexportedStringValue: "$VALUE", + ExportedStringValue: "replaced_value", + }, config) +} diff --git a/internal/otel_collector/config/configauth/README.md b/internal/otel_collector/config/configauth/README.md new file mode 100644 index 00000000000..18331b1a0fd --- /dev/null +++ b/internal/otel_collector/config/configauth/README.md @@ -0,0 +1,17 @@ +# Authentication configuration for receivers + +This module allows server types, such as gRPC and HTTP, to be configured to perform authentication for requests and/or RPCs. Each server type is responsible for getting the request/RPC metadata and passing down to the authenticator. Currently, only bearer token authentication is supported, although the module is ready to accept new authenticators. + +Examples: +```yaml +receivers: + somereceiver: + grpc: + authentication: + attribute: authorization + oidc: + issuer_url: https://auth.example.com/ + issuer_ca_path: /etc/pki/tls/cert.pem + client_id: my-oidc-client + username_claim: email +``` diff --git a/internal/otel_collector/config/configauth/authenticator.go b/internal/otel_collector/config/configauth/authenticator.go new file mode 100644 index 00000000000..62325a3e91e --- /dev/null +++ b/internal/otel_collector/config/configauth/authenticator.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "context" + "errors" + "io" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +var ( + errNoOIDCProvided = errors.New("no OIDC information provided") + errMetadataNotFound = errors.New("no request metadata found") + defaultAttribute = "authorization" +) + +// Authenticator will authenticate the incoming request/RPC +type Authenticator interface { + io.Closer + + // Authenticate checks whether the given context contains valid auth data. Successfully authenticated calls will always return a nil error and a context with the auth data. + Authenticate(context.Context, map[string][]string) (context.Context, error) + + // Start will + Start(context.Context) error + + // UnaryInterceptor is a helper method to provide a gRPC-compatible UnaryInterceptor, typically calling the authenticator's Authenticate method. + UnaryInterceptor(context.Context, interface{}, *grpc.UnaryServerInfo, grpc.UnaryHandler) (interface{}, error) + + // StreamInterceptor is a helper method to provide a gRPC-compatible StreamInterceptor, typically calling the authenticator's Authenticate method. + StreamInterceptor(interface{}, grpc.ServerStream, *grpc.StreamServerInfo, grpc.StreamHandler) error +} + +type authenticateFunc func(context.Context, map[string][]string) (context.Context, error) +type unaryInterceptorFunc func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, authenticate authenticateFunc) (interface{}, error) +type streamInterceptorFunc func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, authenticate authenticateFunc) error + +// NewAuthenticator creates an authenticator based on the given configuration +func NewAuthenticator(cfg Authentication) (Authenticator, error) { + if cfg.OIDC == nil { + return nil, errNoOIDCProvided + } + + if len(cfg.Attribute) == 0 { + cfg.Attribute = defaultAttribute + } + + return newOIDCAuthenticator(cfg) +} + +func defaultUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, authenticate authenticateFunc) (interface{}, error) { + headers, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, errMetadataNotFound + } + + ctx, err := authenticate(ctx, headers) + if err != nil { + return nil, err + } + + return handler(ctx, req) +} + +func defaultStreamInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, authenticate authenticateFunc) error { + ctx := stream.Context() + headers, ok := metadata.FromIncomingContext(ctx) + if !ok { + return errMetadataNotFound + } + + // TODO: how to replace the context from the stream? + _, err := authenticate(ctx, headers) + if err != nil { + return err + } + + return handler(srv, stream) +} diff --git a/internal/otel_collector/config/configauth/authenticator_test.go b/internal/otel_collector/config/configauth/authenticator_test.go new file mode 100644 index 00000000000..b148485285e --- /dev/null +++ b/internal/otel_collector/config/configauth/authenticator_test.go @@ -0,0 +1,195 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +func TestNewAuthenticator(t *testing.T) { + // test + p, err := NewAuthenticator(Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com", + }, + }) + + // verify + assert.NotNil(t, p) + assert.NoError(t, err) +} + +func TestMissingOIDC(t *testing.T) { + // test + p, err := NewAuthenticator(Authentication{}) + + // verify + assert.Nil(t, p) + assert.Equal(t, errNoOIDCProvided, err) +} + +func TestDefaultUnaryInterceptorAuthSucceeded(t *testing.T) { + // prepare + handlerCalled := false + authCalled := false + authFunc := func(context.Context, map[string][]string) (context.Context, error) { + authCalled = true + return context.Background(), nil + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCalled = true + return nil, nil + } + ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")) + + // test + res, err := defaultUnaryInterceptor(ctx, nil, &grpc.UnaryServerInfo{}, handler, authFunc) + + // verify + assert.Nil(t, res) + assert.NoError(t, err) + assert.True(t, authCalled) + assert.True(t, handlerCalled) +} + +func TestDefaultUnaryInterceptorAuthFailure(t *testing.T) { + // prepare + authCalled := false + expectedErr := fmt.Errorf("not authenticated") + authFunc := func(context.Context, map[string][]string) (context.Context, error) { + authCalled = true + return context.Background(), expectedErr + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + assert.FailNow(t, "the handler should not have been called on auth failure!") + return nil, nil + } + ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")) + + // test + res, err := defaultUnaryInterceptor(ctx, nil, &grpc.UnaryServerInfo{}, handler, authFunc) + + // verify + assert.Nil(t, res) + assert.Equal(t, expectedErr, err) + assert.True(t, authCalled) +} + +func TestDefaultUnaryInterceptorMissingMetadata(t *testing.T) { + // prepare + authFunc := func(context.Context, map[string][]string) (context.Context, error) { + assert.FailNow(t, "the auth func should not have been called!") + return context.Background(), nil + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + assert.FailNow(t, "the handler should not have been called!") + return nil, nil + } + + // test + res, err := defaultUnaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{}, handler, authFunc) + + // verify + assert.Nil(t, res) + assert.Equal(t, errMetadataNotFound, err) +} + +func TestDefaultStreamInterceptorAuthSucceeded(t *testing.T) { + // prepare + handlerCalled := false + authCalled := false + authFunc := func(context.Context, map[string][]string) (context.Context, error) { + authCalled = true + return context.Background(), nil + } + handler := func(srv interface{}, stream grpc.ServerStream) error { + handlerCalled = true + return nil + } + ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")) + streamServer := &mockServerStream{ + ctx: ctx, + } + + // test + err := defaultStreamInterceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler, authFunc) + + // verify + assert.NoError(t, err) + assert.True(t, authCalled) + assert.True(t, handlerCalled) +} + +func TestDefaultStreamInterceptorAuthFailure(t *testing.T) { + // prepare + authCalled := false + expectedErr := fmt.Errorf("not authenticated") + authFunc := func(context.Context, map[string][]string) (context.Context, error) { + authCalled = true + return context.Background(), expectedErr + } + handler := func(srv interface{}, stream grpc.ServerStream) error { + assert.FailNow(t, "the handler should not have been called on auth failure!") + return nil + } + ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")) + streamServer := &mockServerStream{ + ctx: ctx, + } + + // test + err := defaultStreamInterceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler, authFunc) + + // verify + assert.Equal(t, expectedErr, err) + assert.True(t, authCalled) +} + +func TestDefaultStreamInterceptorMissingMetadata(t *testing.T) { + // prepare + authFunc := func(context.Context, map[string][]string) (context.Context, error) { + assert.FailNow(t, "the auth func should not have been called!") + return context.Background(), nil + } + handler := func(srv interface{}, stream grpc.ServerStream) error { + assert.FailNow(t, "the handler should not have been called!") + return nil + } + streamServer := &mockServerStream{ + ctx: context.Background(), + } + + // test + err := defaultStreamInterceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler, authFunc) + + // verify + assert.Equal(t, errMetadataNotFound, err) +} + +type mockServerStream struct { + grpc.ServerStream + ctx context.Context +} + +func (m *mockServerStream) Context() context.Context { + return m.ctx +} diff --git a/internal/otel_collector/config/configauth/configauth.go b/internal/otel_collector/config/configauth/configauth.go new file mode 100644 index 00000000000..b76597ac901 --- /dev/null +++ b/internal/otel_collector/config/configauth/configauth.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "context" + + "google.golang.org/grpc" +) + +// Authentication defines the auth settings for the receiver +type Authentication struct { + // The attribute (header name) to look for auth data. Optional, default value: "authentication". + Attribute string `mapstructure:"attribute"` + + // OIDC configures this receiver to use the given OIDC provider as the backend for the authentication mechanism. + // Required. + OIDC *OIDC `mapstructure:"oidc"` +} + +// OIDC defines the OpenID Connect properties for this processor +type OIDC struct { + // IssuerURL is the base URL for the OIDC provider. + // Required. + IssuerURL string `mapstructure:"issuer_url"` + + // Audience of the token, used during the verification. + // For example: "https://accounts.google.com" or "https://login.salesforce.com". + // Required. + Audience string `mapstructure:"audience"` + + // The local path for the issuer CA's TLS server cert. + // Optional. + IssuerCAPath string `mapstructure:"issuer_ca_path"` + + // The claim to use as the username, in case the token's 'sub' isn't the suitable source. + // Optional. + UsernameClaim string `mapstructure:"username_claim"` + + // The claim that holds the subject's group membership information. + // Optional. + GroupsClaim string `mapstructure:"groups_claim"` +} + +// ToServerOptions builds a set of server options ready to be used by the gRPC server +func (a *Authentication) ToServerOptions() ([]grpc.ServerOption, error) { + auth, err := NewAuthenticator(*a) + if err != nil { + return nil, err + } + + // perhaps we should use a timeout here? + // TODO: we need a hook to call auth.Close() + if err := auth.Start(context.Background()); err != nil { + return nil, err + } + + return []grpc.ServerOption{ + grpc.UnaryInterceptor(auth.UnaryInterceptor), + grpc.StreamInterceptor(auth.StreamInterceptor), + }, nil +} diff --git a/internal/otel_collector/config/configauth/configauth_test.go b/internal/otel_collector/config/configauth/configauth_test.go new file mode 100644 index 00000000000..c04efaf15d8 --- /dev/null +++ b/internal/otel_collector/config/configauth/configauth_test.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestToServerOptions(t *testing.T) { + // prepare + oidcServer, err := newOIDCServer() + require.NoError(t, err) + oidcServer.Start() + defer oidcServer.Close() + + config := Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + GroupsClaim: "memberships", + }, + } + + // test + opts, err := config.ToServerOptions() + + // verify + assert.NoError(t, err) + assert.NotNil(t, opts) + assert.Len(t, opts, 2) // we have two interceptors +} + +func TestInvalidConfigurationFailsOnToServerOptions(t *testing.T) { + + for _, tt := range []struct { + cfg Authentication + }{ + { + Authentication{}, + }, + { + Authentication{ + OIDC: &OIDC{ + IssuerURL: "http://oidc.acme.invalid", + Audience: "unit-test", + GroupsClaim: "memberships", + }, + }, + }, + } { + // test + opts, err := tt.cfg.ToServerOptions() + + // verify + assert.Error(t, err) + assert.Nil(t, opts) + } + +} diff --git a/internal/otel_collector/config/configauth/context.go b/internal/otel_collector/config/configauth/context.go new file mode 100644 index 00000000000..a7e9eb2376c --- /dev/null +++ b/internal/otel_collector/config/configauth/context.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import "context" + +var ( + subjectKey = subjectType{} + groupsKey = groupsType{} +) + +type subjectType struct{} +type groupsType struct{} + +// SubjectFromContext returns a list of groups the subject in the context belongs to +func SubjectFromContext(ctx context.Context) (string, bool) { + value, ok := ctx.Value(subjectKey).(string) + return value, ok +} + +// GroupsFromContext returns a list of groups the subject in the context belongs to +func GroupsFromContext(ctx context.Context) ([]string, bool) { + value, ok := ctx.Value(groupsKey).([]string) + return value, ok +} diff --git a/internal/otel_collector/config/configauth/context_test.go b/internal/otel_collector/config/configauth/context_test.go new file mode 100644 index 00000000000..61dec7ab0ba --- /dev/null +++ b/internal/otel_collector/config/configauth/context_test.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSubjectFromContext(t *testing.T) { + // prepare + ctx := context.WithValue(context.Background(), subjectKey, "my-subject") + + // test + sub, ok := SubjectFromContext(ctx) + + // verify + assert.Equal(t, "my-subject", sub) + assert.True(t, ok) +} + +func TestSubjectFromContextNotPresent(t *testing.T) { + // prepare + ctx := context.Background() + + // test + sub, ok := SubjectFromContext(ctx) + + // verify + assert.False(t, ok) + assert.Empty(t, sub) +} + +func TestSubjectFromContextWrongType(t *testing.T) { + // prepare + ctx := context.WithValue(context.Background(), subjectKey, 123) + + // test + sub, ok := SubjectFromContext(ctx) + + // verify + assert.False(t, ok) + assert.Empty(t, sub) +} + +func TestGroupsFromContext(t *testing.T) { + // prepare + ctx := context.WithValue(context.Background(), groupsKey, []string{"my-groups"}) + + // test + groups, ok := GroupsFromContext(ctx) + + // verify + assert.Equal(t, []string{"my-groups"}, groups) + assert.True(t, ok) +} + +func TestGroupsFromContextNotPresent(t *testing.T) { + // prepare + ctx := context.Background() + + // test + sub, ok := GroupsFromContext(ctx) + + // verify + assert.False(t, ok) + assert.Empty(t, sub) +} + +func TestGroupsFromContextWrongType(t *testing.T) { + // prepare + ctx := context.WithValue(context.Background(), subjectKey, 123) + + // test + sub, ok := GroupsFromContext(ctx) + + // verify + assert.False(t, ok) + assert.Empty(t, sub) +} diff --git a/internal/otel_collector/config/configauth/oidc_authenticator.go b/internal/otel_collector/config/configauth/oidc_authenticator.go new file mode 100644 index 00000000000..a0deadcd03a --- /dev/null +++ b/internal/otel_collector/config/configauth/oidc_authenticator.go @@ -0,0 +1,245 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "strings" + "time" + + "github.com/coreos/go-oidc" + "google.golang.org/grpc" +) + +type oidcAuthenticator struct { + attribute string + config OIDC + provider *oidc.Provider + verifier *oidc.IDTokenVerifier + + unaryInterceptor unaryInterceptorFunc + streamInterceptor streamInterceptorFunc +} + +var ( + _ Authenticator = (*oidcAuthenticator)(nil) + + errNoClientIDProvided = errors.New("no ClientID provided for the OIDC configuration") + errNoIssuerURL = errors.New("no IssuerURL provided for the OIDC configuration") + errInvalidAuthenticationHeaderFormat = errors.New("invalid authorization header format") + errFailedToObtainClaimsFromToken = errors.New("failed to get the subject from the token issued by the OIDC provider") + errClaimNotFound = errors.New("username claim from the OIDC configuration not found on the token returned by the OIDC provider") + errUsernameNotString = errors.New("the username returned by the OIDC provider isn't a regular string") + errGroupsClaimNotFound = errors.New("groups claim from the OIDC configuration not found on the token returned by the OIDC provider") + errNotAuthenticated = errors.New("authentication didn't succeed") +) + +func newOIDCAuthenticator(cfg Authentication) (*oidcAuthenticator, error) { + if cfg.OIDC.Audience == "" { + return nil, errNoClientIDProvided + } + if cfg.OIDC.IssuerURL == "" { + return nil, errNoIssuerURL + } + if cfg.Attribute == "" { + cfg.Attribute = defaultAttribute + } + + return &oidcAuthenticator{ + attribute: cfg.Attribute, + config: *cfg.OIDC, + unaryInterceptor: defaultUnaryInterceptor, + streamInterceptor: defaultStreamInterceptor, + }, nil +} + +func (o *oidcAuthenticator) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) { + authHeaders := headers[o.attribute] + if len(authHeaders) == 0 { + return ctx, errNotAuthenticated + } + + // we only use the first header, if multiple values exist + parts := strings.Split(authHeaders[0], " ") + if len(parts) != 2 { + return ctx, errInvalidAuthenticationHeaderFormat + } + + idToken, err := o.verifier.Verify(ctx, parts[1]) + if err != nil { + return ctx, fmt.Errorf("failed to verify token: %w", err) + } + + claims := map[string]interface{}{} + if err = idToken.Claims(&claims); err != nil { + // currently, this isn't a valid condition, the Verify call a few lines above + // will already attempt to parse the payload as a json and set it as the claims + // for the token. As we are using a map to hold the claims, there's no way to fail + // to read the claims. It could fail if we were using a custom struct. Instead of + // swalling the error, it's better to make this future-proof, in case the underlying + // code changes + return ctx, errFailedToObtainClaimsFromToken + } + + sub, err := getSubjectFromClaims(claims, o.config.UsernameClaim, idToken.Subject) + if err != nil { + return ctx, fmt.Errorf("failed to get subject from claims in the token: %w", err) + } + ctx = context.WithValue(ctx, subjectKey, sub) + + gr, err := getGroupsFromClaims(claims, o.config.GroupsClaim) + if err != nil { + return ctx, fmt.Errorf("failed to get groups from claims in the token: %w", err) + } + ctx = context.WithValue(ctx, groupsKey, gr) + + return ctx, nil +} + +func (o *oidcAuthenticator) Start(context.Context) error { + provider, err := getProviderForConfig(o.config) + if err != nil { + return fmt.Errorf("failed to get configuration from the auth server: %w", err) + } + o.provider = provider + + o.verifier = o.provider.Verifier(&oidc.Config{ + ClientID: o.config.Audience, + }) + + return nil +} + +func (o *oidcAuthenticator) Close() error { + // no-op at the moment + // once we implement caching of the tokens we might need this + return nil +} + +func (o *oidcAuthenticator) UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return o.unaryInterceptor(ctx, req, info, handler, o.Authenticate) +} + +func (o *oidcAuthenticator) StreamInterceptor(srv interface{}, str grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return o.streamInterceptor(srv, str, info, handler, o.Authenticate) +} + +func getSubjectFromClaims(claims map[string]interface{}, usernameClaim string, fallback string) (string, error) { + if len(usernameClaim) > 0 { + username, found := claims[usernameClaim] + if !found { + return "", errClaimNotFound + } + + sUsername, ok := username.(string) + if !ok { + return "", errUsernameNotString + } + + return sUsername, nil + } + + return fallback, nil +} + +func getGroupsFromClaims(claims map[string]interface{}, groupsClaim string) ([]string, error) { + if len(groupsClaim) > 0 { + var groups []string + rawGroup, ok := claims[groupsClaim] + if !ok { + return nil, errGroupsClaimNotFound + } + switch v := rawGroup.(type) { + case string: + groups = append(groups, v) + case []string: + groups = v + case []interface{}: + groups = make([]string, 0, len(v)) + for i := range v { + groups = append(groups, fmt.Sprintf("%v", v[i])) + } + } + + return groups, nil + } + + return []string{}, nil +} + +func getProviderForConfig(config OIDC) (*oidc.Provider, error) { + t := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 10 * time.Second, + DualStack: true, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 5 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + cert, err := getIssuerCACertFromPath(config.IssuerCAPath) + if err != nil { + return nil, err // the errors from this path have enough context already + } + + if cert != nil { + t.TLSClientConfig = &tls.Config{ + RootCAs: x509.NewCertPool(), + } + t.TLSClientConfig.RootCAs.AddCert(cert) + } + + client := &http.Client{ + Timeout: 5 * time.Second, + Transport: t, + } + oidcContext := oidc.ClientContext(context.Background(), client) + return oidc.NewProvider(oidcContext, config.IssuerURL) +} + +func getIssuerCACertFromPath(path string) (*x509.Certificate, error) { + if path == "" { + return nil, nil + } + + rawCA, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("could not read the CA file %q: %w", path, err) + } + + if len(rawCA) == 0 { + return nil, fmt.Errorf("could not read the CA file %q: empty file", path) + } + + block, _ := pem.Decode(rawCA) + if block == nil { + return nil, fmt.Errorf("cannot decode the contents of the CA file %q: %w", path, err) + } + + return x509.ParseCertificate(block.Bytes) +} diff --git a/internal/otel_collector/config/configauth/oidc_authenticator_test.go b/internal/otel_collector/config/configauth/oidc_authenticator_test.go new file mode 100644 index 00000000000..7f4879c6c28 --- /dev/null +++ b/internal/otel_collector/config/configauth/oidc_authenticator_test.go @@ -0,0 +1,548 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "math/big" + "net" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +func TestOIDCAuthenticationSucceeded(t *testing.T) { + // prepare + oidcServer, err := newOIDCServer() + require.NoError(t, err) + oidcServer.Start() + defer oidcServer.Close() + + config := Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + GroupsClaim: "memberships", + }, + } + p, err := newOIDCAuthenticator(config) + require.NoError(t, err) + + err = p.Start(context.Background()) + require.NoError(t, err) + + payload, _ := json.Marshal(map[string]interface{}{ + "sub": "jdoe@example.com", + "name": "jdoe", + "iss": oidcServer.URL, + "aud": "unit-test", + "exp": time.Now().Add(time.Minute).Unix(), + "memberships": []string{"department-1", "department-2"}, + }) + token, err := oidcServer.token(payload) + require.NoError(t, err) + + // test + ctx, err := p.Authenticate(context.Background(), map[string][]string{"authorization": {fmt.Sprintf("Bearer %s", token)}}) + + // verify + assert.NotNil(t, ctx) + assert.NoError(t, err) + + subject, ok := SubjectFromContext(ctx) + assert.True(t, ok) + assert.EqualValues(t, "jdoe@example.com", subject) + + groups, ok := GroupsFromContext(ctx) + assert.True(t, ok) + assert.Contains(t, groups, "department-1") + assert.Contains(t, groups, "department-2") +} + +func TestOIDCProviderForConfigWithTLS(t *testing.T) { + // prepare the CA cert for the TLS handler + cert := x509.Certificate{ + NotBefore: time.Now(), + NotAfter: time.Now().Add(10 * time.Second), + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, + SerialNumber: big.NewInt(9447457), // some number + } + priv, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + x509Cert, err := x509.CreateCertificate(rand.Reader, &cert, &cert, &priv.PublicKey, priv) + require.NoError(t, err) + + caFile, err := ioutil.TempFile(os.TempDir(), "cert") + require.NoError(t, err) + defer os.Remove(caFile.Name()) + + err = pem.Encode(caFile, &pem.Block{ + Type: "CERTIFICATE", + Bytes: x509Cert, + }) + require.NoError(t, err) + + oidcServer, err := newOIDCServer() + require.NoError(t, err) + defer oidcServer.Close() + + tlsCert := tls.Certificate{ + Certificate: [][]byte{x509Cert}, + PrivateKey: priv, + } + oidcServer.TLS = &tls.Config{Certificates: []tls.Certificate{tlsCert}} + oidcServer.StartTLS() + + // prepare the processor configuration + config := OIDC{ + IssuerURL: oidcServer.URL, + IssuerCAPath: caFile.Name(), + Audience: "unit-test", + } + + // test + provider, err := getProviderForConfig(config) + + // verify + assert.NoError(t, err) + assert.NotNil(t, provider) +} + +func TestOIDCLoadIssuerCAFromPath(t *testing.T) { + // prepare + cert := x509.Certificate{ + SerialNumber: big.NewInt(9447457), // some number + IsCA: true, + } + priv, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + x509Cert, err := x509.CreateCertificate(rand.Reader, &cert, &cert, &priv.PublicKey, priv) + require.NoError(t, err) + + file, err := ioutil.TempFile(os.TempDir(), "cert") + require.NoError(t, err) + defer os.Remove(file.Name()) + + err = pem.Encode(file, &pem.Block{ + Type: "CERTIFICATE", + Bytes: x509Cert, + }) + require.NoError(t, err) + + // test + loaded, err := getIssuerCACertFromPath(file.Name()) + + // verify + assert.NoError(t, err) + assert.Equal(t, cert.SerialNumber, loaded.SerialNumber) +} + +func TestOIDCFailedToLoadIssuerCAFromPathEmptyCert(t *testing.T) { + // prepare + file, err := ioutil.TempFile(os.TempDir(), "cert") + require.NoError(t, err) + defer os.Remove(file.Name()) + + // test + loaded, err := getIssuerCACertFromPath(file.Name()) // the file exists, but the contents isn't a cert + + // verify + assert.Error(t, err) + assert.Nil(t, loaded) +} + +func TestOIDCFailedToLoadIssuerCAFromPathMissingFile(t *testing.T) { + // test + loaded, err := getIssuerCACertFromPath("some-non-existing-file") + + // verify + assert.Error(t, err) + assert.Nil(t, loaded) +} + +func TestOIDCFailedToLoadIssuerCAFromPathInvalidContent(t *testing.T) { + // prepare + file, err := ioutil.TempFile(os.TempDir(), "cert") + require.NoError(t, err) + defer os.Remove(file.Name()) + file.Write([]byte("foobar")) + + config := OIDC{ + IssuerCAPath: file.Name(), + } + + // test + provider, err := getProviderForConfig(config) // cross test with getIssuerCACertFromPath + + // verify + assert.Error(t, err) + assert.Nil(t, provider) +} + +func TestOIDCInvalidAuthHeader(t *testing.T) { + // prepare + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com", + }, + }) + require.NoError(t, err) + + // test + ctx, err := p.Authenticate(context.Background(), map[string][]string{"authorization": {"some-value"}}) + + // verify + assert.NotNil(t, ctx) + assert.Equal(t, errInvalidAuthenticationHeaderFormat, err) +} + +func TestOIDCNotAuthenticated(t *testing.T) { + // prepare + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com", + }, + }) + require.NoError(t, err) + + // test + ctx, err := p.Authenticate(context.Background(), make(map[string][]string)) + + // verify + assert.NotNil(t, ctx) + assert.Equal(t, errNotAuthenticated, err) +} + +func TestProviderNotReacheable(t *testing.T) { + // prepare + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com", + }, + }) + require.NoError(t, err) + + // test + err = p.Start(context.Background()) + + // verify + assert.Error(t, err) +} + +func TestFailedToVerifyToken(t *testing.T) { + // prepare + oidcServer, err := newOIDCServer() + require.NoError(t, err) + oidcServer.Start() + defer oidcServer.Close() + + p, err := newOIDCAuthenticator(Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + }, + }) + require.NoError(t, err) + + err = p.Start(context.Background()) + require.NoError(t, err) + + // test + ctx, err := p.Authenticate(context.Background(), map[string][]string{"authorization": {"Bearer some-token"}}) + + // verify + assert.NotNil(t, ctx) + assert.Error(t, err) +} + +func TestFailedToGetGroupsClaimFromToken(t *testing.T) { + // prepare + oidcServer, err := newOIDCServer() + require.NoError(t, err) + oidcServer.Start() + defer oidcServer.Close() + + for _, tt := range []struct { + casename string + config Authentication + expectedError error + }{ + { + "groupsClaimNonExisting", + Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + GroupsClaim: "non-existing-claim", + }, + }, + errGroupsClaimNotFound, + }, + { + "usernameClaimNonExisting", + Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + UsernameClaim: "non-existing-claim", + }, + }, + errClaimNotFound, + }, + { + "usernameNotString", + Authentication{ + OIDC: &OIDC{ + IssuerURL: oidcServer.URL, + Audience: "unit-test", + UsernameClaim: "some-non-string-field", + }, + }, + errUsernameNotString, + }, + } { + t.Run(tt.casename, func(t *testing.T) { + p, err := newOIDCAuthenticator(tt.config) + require.NoError(t, err) + + err = p.Start(context.Background()) + require.NoError(t, err) + + payload, _ := json.Marshal(map[string]interface{}{ + "iss": oidcServer.URL, + "some-non-string-field": 123, + "aud": "unit-test", + "exp": time.Now().Add(time.Minute).Unix(), + }) + token, err := oidcServer.token(payload) + require.NoError(t, err) + + // test + ctx, err := p.Authenticate(context.Background(), map[string][]string{"authorization": {fmt.Sprintf("Bearer %s", token)}}) + + // verify + assert.NotNil(t, ctx) + assert.True(t, errors.Is(err, tt.expectedError)) + }) + } +} + +func TestSubjectFromClaims(t *testing.T) { + // prepare + claims := map[string]interface{}{ + "username": "jdoe", + } + + // test + username, err := getSubjectFromClaims(claims, "username", "") + + // verify + assert.NoError(t, err) + assert.Equal(t, "jdoe", username) +} + +func TestSubjectFallback(t *testing.T) { + // prepare + claims := map[string]interface{}{ + "sub": "jdoe", + } + + // test + username, err := getSubjectFromClaims(claims, "", "jdoe") + + // verify + assert.NoError(t, err) + assert.Equal(t, "jdoe", username) +} + +func TestGroupsFromClaim(t *testing.T) { + // prepare + for _, tt := range []struct { + casename string + input interface{} + expected []string + }{ + { + "single-string", + "department-1", + []string{"department-1"}, + }, + { + "multiple-strings", + []string{"department-1", "department-2"}, + []string{"department-1", "department-2"}, + }, + { + "multiple-things", + []interface{}{"department-1", 123}, + []string{"department-1", "123"}, + }, + } { + t.Run(tt.casename, func(t *testing.T) { + claims := map[string]interface{}{ + "sub": "jdoe", + "memberships": tt.input, + } + + // test + groups, err := getGroupsFromClaims(claims, "memberships") + assert.NoError(t, err) + assert.Equal(t, tt.expected, groups) + }) + } +} + +func TestEmptyGroupsClaim(t *testing.T) { + // prepare + claims := map[string]interface{}{ + "sub": "jdoe", + } + + // test + groups, err := getGroupsFromClaims(claims, "") + assert.NoError(t, err) + assert.Equal(t, []string{}, groups) +} + +func TestMissingClient(t *testing.T) { + // prepare + config := Authentication{ + OIDC: &OIDC{ + IssuerURL: "http://example.com/", + }, + } + + // test + p, err := newOIDCAuthenticator(config) + + // verify + assert.Nil(t, p) + assert.Equal(t, errNoClientIDProvided, err) +} + +func TestMissingIssuerURL(t *testing.T) { + // prepare + config := Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + }, + } + + // test + p, err := newOIDCAuthenticator(config) + + // verify + assert.Nil(t, p) + assert.Equal(t, errNoIssuerURL, err) +} + +func TestClose(t *testing.T) { + // prepare + config := Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com/", + }, + } + p, err := newOIDCAuthenticator(config) + require.NoError(t, err) + require.NotNil(t, p) + + // test + err = p.Close() // for now, we never fail + + // verify + assert.NoError(t, err) +} + +func TestUnaryInterceptor(t *testing.T) { + // prepare + config := Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com/", + }, + } + p, err := newOIDCAuthenticator(config) + require.NoError(t, err) + require.NotNil(t, p) + + interceptorCalled := false + p.unaryInterceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, authenticate authenticateFunc) (interface{}, error) { + interceptorCalled = true + return nil, nil + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, nil + } + + // test + res, err := p.UnaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{}, handler) + + // verify + assert.NoError(t, err) + assert.Nil(t, res) + assert.True(t, interceptorCalled) +} + +func TestStreamInterceptor(t *testing.T) { + // prepare + config := Authentication{ + OIDC: &OIDC{ + Audience: "some-audience", + IssuerURL: "http://example.com/", + }, + } + p, err := newOIDCAuthenticator(config) + require.NoError(t, err) + require.NotNil(t, p) + + interceptorCalled := false + p.streamInterceptor = func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, authenticate authenticateFunc) error { + interceptorCalled = true + return nil + } + handler := func(srv interface{}, stream grpc.ServerStream) error { + return nil + } + streamServer := &mockServerStream{ + ctx: context.Background(), + } + + // test + err = p.StreamInterceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler) + + // verify + assert.NoError(t, err) + assert.True(t, interceptorCalled) +} diff --git a/internal/otel_collector/config/configauth/oidc_server_test.go b/internal/otel_collector/config/configauth/oidc_server_test.go new file mode 100644 index 00000000000..3faac34f09b --- /dev/null +++ b/internal/otel_collector/config/configauth/oidc_server_test.go @@ -0,0 +1,136 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configauth + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" // #nosec + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "time" +) + +// oidcServer is an overly simplified OIDC mock server, good enough to sign the tokens required by the test +// and pass the verification done by the underlying libraries +type oidcServer struct { + *httptest.Server + x509Cert []byte + privateKey *rsa.PrivateKey +} + +func newOIDCServer() (*oidcServer, error) { + jwks := map[string]interface{}{} + + mux := http.NewServeMux() + server := httptest.NewUnstartedServer(mux) + + mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(w).Encode(map[string]interface{}{ + "issuer": server.URL, + "jwks_uri": fmt.Sprintf("%s/.well-known/jwks.json", server.URL), + }) + }) + mux.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(w).Encode(jwks) + }) + + privateKey, err := createPrivateKey() + if err != nil { + return nil, err + } + + x509Cert, err := createCertificate(privateKey) + if err != nil { + return nil, err + } + + eBytes := make([]byte, 8) + binary.BigEndian.PutUint64(eBytes, uint64(privateKey.E)) + eBytes = bytes.TrimLeft(eBytes, "\x00") + + // #nosec + sum := sha1.Sum(x509Cert) + jwks["keys"] = []map[string]interface{}{{ + "alg": "RS256", + "kty": "RSA", + "use": "sig", + "x5c": []string{base64.StdEncoding.EncodeToString(x509Cert)}, + "n": base64.RawURLEncoding.EncodeToString(privateKey.N.Bytes()), + "e": base64.RawURLEncoding.EncodeToString(eBytes), + "kid": base64.RawURLEncoding.EncodeToString(sum[:]), + "x5t": base64.RawURLEncoding.EncodeToString(sum[:]), + }} + + return &oidcServer{server, x509Cert, privateKey}, nil +} + +func (s *oidcServer) token(jsonPayload []byte) (string, error) { + jsonHeader, _ := json.Marshal(map[string]interface{}{ + "alg": "RS256", + "typ": "JWT", + }) + + header := base64.RawURLEncoding.EncodeToString(jsonHeader) + payload := base64.RawURLEncoding.EncodeToString(jsonPayload) + digest := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", header, payload))) + + signature, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, crypto.SHA256, digest[:]) + if err != nil { + return "", err + } + + encodedSignature := base64.RawURLEncoding.EncodeToString(signature) + token := fmt.Sprintf("%s.%s.%s", header, payload, encodedSignature) + return token, nil +} + +func createCertificate(privateKey *rsa.PrivateKey) ([]byte, error) { + cert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Ecorp, Inc"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(5 * time.Minute), + } + + x509Cert, err := x509.CreateCertificate(rand.Reader, &cert, &cert, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, err + } + + return x509Cert, nil +} + +func createPrivateKey() (*rsa.PrivateKey, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + return priv, nil +} diff --git a/internal/otel_collector/config/configcheck/configcheck.go b/internal/otel_collector/config/configcheck/configcheck.go new file mode 100644 index 00000000000..5d344f28671 --- /dev/null +++ b/internal/otel_collector/config/configcheck/configcheck.go @@ -0,0 +1,193 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package configcheck has checks to be applied to configuration +// objects implemented by factories of components used in the OpenTelemetry +// collector. It is recommended for implementers of components to run the +// validations available on this package. +package configcheck + +import ( + "fmt" + "reflect" + "regexp" + "strings" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" +) + +// The regular expression for valid config field tag. +var configFieldTagRegExp = regexp.MustCompile("^[a-z0-9][a-z0-9_]*$") + +// ValidateConfigFromFactories checks if all configurations for the given factories +// are satisfying the patterns used by the collector. +func ValidateConfigFromFactories(factories component.Factories) error { + var errs []error + var configs []interface{} + + for _, factory := range factories.Receivers { + configs = append(configs, factory.CreateDefaultConfig()) + } + for _, factory := range factories.Processors { + configs = append(configs, factory.CreateDefaultConfig()) + } + for _, factory := range factories.Exporters { + configs = append(configs, factory.CreateDefaultConfig()) + } + for _, factory := range factories.Extensions { + configs = append(configs, factory.CreateDefaultConfig()) + } + + for _, config := range configs { + if err := ValidateConfig(config); err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} + +// ValidateConfig enforces that given configuration object is following the patterns +// used by the collector. This ensures consistency between different implementations +// of components and extensions. It is recommended for implementers of components +// to call this function on their tests passing the default configuration of the +// component factory. +func ValidateConfig(config interface{}) error { + t := reflect.TypeOf(config) + + tk := t.Kind() + if t.Kind() == reflect.Ptr { + t = t.Elem() + tk = t.Kind() + } + + if tk != reflect.Struct { + return fmt.Errorf( + "config must be a struct or a pointer to one, the passed object is a %s", + tk) + } + + return validateConfigDataType(t) +} + +// validateConfigDataType performs a descending validation of the given type. +// If the type is a struct it goes to each of its fields to check for the proper +// tags. +func validateConfigDataType(t reflect.Type) error { + var errs []error + + switch t.Kind() { + case reflect.Ptr: + if err := validateConfigDataType(t.Elem()); err != nil { + errs = append(errs, err) + } + case reflect.Struct: + // Reflect on the pointed data and check each of its fields. + nf := t.NumField() + for i := 0; i < nf; i++ { + f := t.Field(i) + if err := checkStructFieldTags(f); err != nil { + errs = append(errs, err) + } + } + default: + // The config object can carry other types but they are not used when + // reading the configuration via viper so ignore them. Basically ignore: + // reflect.Uintptr, reflect.Chan, reflect.Func, reflect.Interface, and + // reflect.UnsafePointer. + } + + if err := componenterror.CombineErrors(errs); err != nil { + return fmt.Errorf( + "type %q from package %q has invalid config settings: %v", + t.Name(), + t.PkgPath(), + err) + } + + return nil +} + +// checkStructFieldTags inspects the tags of a struct field. +func checkStructFieldTags(f reflect.StructField) error { + + tagValue := f.Tag.Get("mapstructure") + if tagValue == "" { + + // Ignore special types. + switch f.Type.Kind() { + case reflect.Interface, reflect.Chan, reflect.Func, reflect.Uintptr, reflect.UnsafePointer: + // Allow the config to carry the types above, but since they are not read + // when loading configuration, just ignore them. + return nil + } + + // Public fields of other types should be tagged. + chars := []byte(f.Name) + if len(chars) > 0 && chars[0] >= 'A' && chars[0] <= 'Z' { + return fmt.Errorf("mapstructure tag not present on field %q", f.Name) + } + + // Not public field, no need to have a tag. + return nil + } + + tagParts := strings.Split(tagValue, ",") + if tagParts[0] != "" { + if tagParts[0] == "-" { + // Nothing to do, as mapstructure decode skips this field. + return nil + } + } + + // Check if squash is specified. + squash := false + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + + if squash { + // Field was squashed. + if f.Type.Kind() != reflect.Struct { + return fmt.Errorf( + "attempt to squash non-struct type on field %q", f.Name) + } + } + + switch f.Type.Kind() { + case reflect.Struct: + // It is another struct, continue down-level + return validateConfigDataType(f.Type) + + case reflect.Map, reflect.Slice, reflect.Array: + // The element of map, array, or slice can be itself a configuration object. + return validateConfigDataType(f.Type.Elem()) + + default: + fieldTag := tagParts[0] + if !configFieldTagRegExp.MatchString(fieldTag) { + return fmt.Errorf( + "field %q has config tag %q which doesn't satisfy %q", + f.Name, + fieldTag, + configFieldTagRegExp.String()) + } + } + + return nil +} diff --git a/internal/otel_collector/config/configcheck/configcheck_test.go b/internal/otel_collector/config/configcheck/configcheck_test.go new file mode 100644 index 00000000000..e8618a963f7 --- /dev/null +++ b/internal/otel_collector/config/configcheck/configcheck_test.go @@ -0,0 +1,203 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configcheck + +import ( + "context" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/service/defaultcomponents" +) + +func TestValidateConfigFromFactories_Success(t *testing.T) { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + err = ValidateConfigFromFactories(factories) + require.NoError(t, err) +} + +func TestValidateConfigFromFactories_Failure(t *testing.T) { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + // Add a factory returning config not following pattern to force error. + f := &badConfigExtensionFactory{} + factories.Extensions[f.Type()] = f + + err = ValidateConfigFromFactories(factories) + require.Error(t, err) +} + +func TestValidateConfigPointerAndValue(t *testing.T) { + config := struct { + SomeFiled string `mapstructure:"test"` + }{} + assert.NoError(t, ValidateConfig(config)) + assert.NoError(t, ValidateConfig(&config)) +} + +func TestValidateConfig(t *testing.T) { + type BadConfigTag struct { + BadTagField int `mapstructure:"test-dash"` + } + + tests := []struct { + name string + config interface{} + wantErrMsgSubStr string + }{ + { + name: "typical_config", + config: struct { + MyPublicString string `mapstructure:"string"` + }{}, + }, + { + name: "private_fields_ignored", + config: struct { + // A public type with proper tag. + MyPublicString string `mapstructure:"string"` + // A public type with proper tag. + MyPublicInt string `mapstructure:"int"` + // A public type that should be ignored. + MyFunc func() error + // A public type that should be ignored. + Reader io.Reader + // private type not tagged. + myPrivateString string + _someInt int + }{}, + }, + { + name: "not_struct_nor_pointer", + config: func(x int) int { + return x * x + }, + wantErrMsgSubStr: "config must be a struct or a pointer to one, the passed object is a func", + }, + { + name: "squash_on_non_struct", + config: struct { + MyInt int `mapstructure:",squash"` + }{}, + wantErrMsgSubStr: "attempt to squash non-struct type on field \"MyInt\"", + }, + { + name: "invalid_tag_detected", + config: BadConfigTag{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "public_field_must_have_tag", + config: struct { + PublicFieldWithoutMapstructureTag string + }{}, + wantErrMsgSubStr: "mapstructure tag not present on field \"PublicFieldWithoutMapstructureTag\"", + }, + { + name: "invalid_map_item", + config: struct { + Map map[string]BadConfigTag `mapstructure:"test_map"` + }{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "invalid_slice_item", + config: struct { + Slice []BadConfigTag `mapstructure:"test_slice"` + }{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "invalid_array_item", + config: struct { + Array [2]BadConfigTag `mapstructure:"test_array"` + }{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "invalid_map_item_ptr", + config: struct { + Map map[string]*BadConfigTag `mapstructure:"test_map"` + }{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "invalid_slice_item_ptr", + config: struct { + Slice []*BadConfigTag `mapstructure:"test_slice"` + }{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "invalid_array_item_ptr", + config: struct { + Array [2]*BadConfigTag `mapstructure:"test_array"` + }{}, + wantErrMsgSubStr: "field \"BadTagField\" has config tag \"test-dash\" which doesn't satisfy", + }, + { + name: "valid_map_item", + config: struct { + Map map[string]int `mapstructure:"test_map"` + }{}, + }, + { + name: "valid_slice_item", + config: struct { + Slice []string `mapstructure:"test_slice"` + }{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateConfig(tt.config) + if tt.wantErrMsgSubStr == "" { + assert.NoError(t, err) + } else { + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), tt.wantErrMsgSubStr)) + } + }) + } +} + +// badConfigExtensionFactory was created to force error path from factory returning +// a config not satisfying the validation. +type badConfigExtensionFactory struct{} + +func (b badConfigExtensionFactory) Type() configmodels.Type { + return "bad_config" +} + +func (b badConfigExtensionFactory) CreateDefaultConfig() configmodels.Extension { + return &struct { + configmodels.ExtensionSettings + BadTagField int `mapstructure:"tag-with-dashes"` + }{} +} + +func (b badConfigExtensionFactory) CreateExtension(_ context.Context, _ component.ExtensionCreateParams, _ configmodels.Extension) (component.ServiceExtension, error) { + return nil, nil +} diff --git a/internal/otel_collector/config/configerror/configerror.go b/internal/otel_collector/config/configerror/configerror.go new file mode 100644 index 00000000000..fe694ad4b34 --- /dev/null +++ b/internal/otel_collector/config/configerror/configerror.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package configerror contains the common errors caused by malformed configs. +package configerror + +import "errors" + +// ErrDataTypeIsNotSupported can be returned by receiver, exporter or processor +// factory methods that create the entity if the particular telemetry +// data type is not supported by the receiver, exporter or processor. +var ErrDataTypeIsNotSupported = errors.New("telemetry type is not supported") diff --git a/internal/otel_collector/config/configgrpc/README.md b/internal/otel_collector/config/configgrpc/README.md new file mode 100644 index 00000000000..4a6ff017b6d --- /dev/null +++ b/internal/otel_collector/config/configgrpc/README.md @@ -0,0 +1,62 @@ +# gRPC Configuration Settings + +gRPC exposes a [variety of settings](https://godoc.org/google.golang.org/grpc). +Several of these settings are available for configuration within individual +receivers or exporters. In general, none of these settings should need to be +adjusted. + +## Client Configuration + +[Exporters](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/README.md) +leverage client configuration. + +Note that client configuration supports TLS configuration, however +configuration parameters are not defined under `tls_settings` like server +configuration. For more information, see [configtls +README](../configtls/README.md). + +- [`balancer_name`](https://github.com/grpc/grpc-go/blob/master/examples/features/load_balancing/README.md) +- `compression` (default = gzip): Compression type to use (only gzip is supported today) +- `endpoint`: Valid value syntax available [here](https://github.com/grpc/grpc/blob/master/doc/naming.md) +- `headers`: name/value pairs added to the request +- [`keepalive`](https://godoc.org/google.golang.org/grpc/keepalive#ClientParameters) + - `permit_without_stream` + - `time` + - `timeout` +- [`read_buffer_size`](https://godoc.org/google.golang.org/grpc#ReadBufferSize) +- [`write_buffer_size`](https://godoc.org/google.golang.org/grpc#WriteBufferSize) + +Example: + +```yaml +exporter: + otlp: + endpoint: otelcol2:55690 + headers: + test1: "value1" + "test 2": "value 2" +``` + +## Server Configuration + +[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/master/receiver/README.md) +leverage server configuration. + +Note that transport configuration can also be configured. For more information, +see [confignet README](../confignet/README.md). + +- [`keepalive`](https://godoc.org/google.golang.org/grpc/keepalive#ServerParameters) + - [`enforcement_policy`](https://godoc.org/google.golang.org/grpc/keepalive#EnforcementPolicy) + - `min_time` + - `permit_without_stream` + - [`server_parameters`](https://godoc.org/google.golang.org/grpc/keepalive#ServerParameters) + - `max_connection_age` + - `max_connection_age_grace` + - `max_connection_idle` + - `time` + - `timeout` +- [`max_concurrent_streams`](https://godoc.org/google.golang.org/grpc#MaxConcurrentStreams) +- [`max_recv_msg_size_mib`](https://godoc.org/google.golang.org/grpc#MaxRecvMsgSize) +- [`read_buffer_size`](https://godoc.org/google.golang.org/grpc#ReadBufferSize) +- [`tls_settings`](../configtls/README.md) +- [`write_buffer_size`](https://godoc.org/google.golang.org/grpc#WriteBufferSize) diff --git a/internal/otel_collector/config/configgrpc/bearer_token.go b/internal/otel_collector/config/configgrpc/bearer_token.go new file mode 100644 index 00000000000..bf457f6dc75 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/bearer_token.go @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configgrpc + +import ( + "context" + "fmt" + + "google.golang.org/grpc/credentials" +) + +var _ credentials.PerRPCCredentials = (*PerRPCAuth)(nil) + +// PerRPCAuth is a gRPC credentials.PerRPCCredentials implementation that returns an 'authorization' header. +type PerRPCAuth struct { + metadata map[string]string +} + +// BearerToken returns a new PerRPCAuth based on the given token. +func BearerToken(t string) *PerRPCAuth { + return &PerRPCAuth{ + metadata: map[string]string{"authorization": fmt.Sprintf("Bearer %s", t)}, + } +} + +// GetRequestMetadata returns the request metadata to be used with the RPC. +func (c *PerRPCAuth) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return c.metadata, nil +} + +// RequireTransportSecurity always returns true for this implementation. Passing bearer tokens in plain-text connections is a bad idea. +func (c *PerRPCAuth) RequireTransportSecurity() bool { + return true +} diff --git a/internal/otel_collector/config/configgrpc/bearer_token_test.go b/internal/otel_collector/config/configgrpc/bearer_token_test.go new file mode 100644 index 00000000000..d91c439496c --- /dev/null +++ b/internal/otel_collector/config/configgrpc/bearer_token_test.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configgrpc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBearerToken(t *testing.T) { + // test + result := BearerToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...") + metadata, err := result.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // verify + assert.Equal(t, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", metadata["authorization"]) +} + +func TestBearerTokenRequiresSecureTransport(t *testing.T) { + // test + token := BearerToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...") + + // verify + assert.True(t, token.RequireTransportSecurity()) +} diff --git a/internal/otel_collector/config/configgrpc/configgrpc.go b/internal/otel_collector/config/configgrpc/configgrpc.go new file mode 100644 index 00000000000..6d0131081c0 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/configgrpc.go @@ -0,0 +1,313 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package configgrpc defines the gRPC configuration settings. +package configgrpc + +import ( + "fmt" + "net" + "strings" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/balancer/roundrobin" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding/gzip" + "google.golang.org/grpc/keepalive" + + "go.opentelemetry.io/collector/config/configauth" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtls" +) + +// Compression gRPC keys for supported compression types within collector +const ( + CompressionUnsupported = "" + CompressionGzip = "gzip" + + PerRPCAuthTypeBearer = "bearer" +) + +var ( + // Map of opentelemetry compression types to grpc registered compression types + grpcCompressionKeyMap = map[string]string{ + CompressionGzip: gzip.Name, + } +) + +// Allowed balancer names to be set in grpclb_policy to discover the servers +var allowedBalancerNames = []string{roundrobin.Name, grpc.PickFirstBalancerName} + +// KeepaliveClientConfig exposes the keepalive.ClientParameters to be used by the exporter. +// Refer to the original data-structure for the meaning of each parameter: +// https://godoc.org/google.golang.org/grpc/keepalive#ClientParameters +type KeepaliveClientConfig struct { + Time time.Duration `mapstructure:"time,omitempty"` + Timeout time.Duration `mapstructure:"timeout,omitempty"` + PermitWithoutStream bool `mapstructure:"permit_without_stream,omitempty"` +} + +// GRPCClientSettings defines common settings for a gRPC client configuration. +type GRPCClientSettings struct { + // The target to which the exporter is going to send traces or metrics, + // using the gRPC protocol. The valid syntax is described at + // https://github.com/grpc/grpc/blob/master/doc/naming.md. + Endpoint string `mapstructure:"endpoint"` + + // The compression key for supported compression types within + // collector. Currently the only supported mode is `gzip`. + Compression string `mapstructure:"compression"` + + // TLSSetting struct exposes TLS client configuration. + TLSSetting configtls.TLSClientSetting `mapstructure:",squash"` + + // The keepalive parameters for gRPC client. See grpc.WithKeepaliveParams + // (https://godoc.org/google.golang.org/grpc#WithKeepaliveParams). + Keepalive *KeepaliveClientConfig `mapstructure:"keepalive"` + + // ReadBufferSize for gRPC client. See grpc.WithReadBufferSize + // (https://godoc.org/google.golang.org/grpc#WithReadBufferSize). + ReadBufferSize int `mapstructure:"read_buffer_size"` + + // WriteBufferSize for gRPC gRPC. See grpc.WithWriteBufferSize + // (https://godoc.org/google.golang.org/grpc#WithWriteBufferSize). + WriteBufferSize int `mapstructure:"write_buffer_size"` + + // WaitForReady parameter configures client to wait for ready state before sending data. + // (https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md) + WaitForReady bool `mapstructure:"wait_for_ready"` + + // The headers associated with gRPC requests. + Headers map[string]string `mapstructure:"headers"` + + // PerRPCAuth parameter configures the client to send authentication data on a per-RPC basis. + PerRPCAuth *PerRPCAuthConfig `mapstructure:"per_rpc_auth"` + + // Sets the balancer in grpclb_policy to discover the servers. Default is pick_first + // https://github.com/grpc/grpc-go/blob/master/examples/features/load_balancing/README.md + BalancerName string `mapstructure:"balancer_name"` +} + +type KeepaliveServerConfig struct { + ServerParameters *KeepaliveServerParameters `mapstructure:"server_parameters,omitempty"` + EnforcementPolicy *KeepaliveEnforcementPolicy `mapstructure:"enforcement_policy,omitempty"` +} + +// PerRPCAuthConfig specifies how the Per-RPC authentication data should be obtained. +type PerRPCAuthConfig struct { + // AuthType represents the authentication type to use. Currently, only 'bearer' is supported. + AuthType string `mapstructure:"type,omitempty"` + + // BearerToken specifies the bearer token to use for every RPC. + BearerToken string `mapstructure:"bearer_token,omitempty"` +} + +// KeepaliveServerParameters allow configuration of the keepalive.ServerParameters. +// The same default values as keepalive.ServerParameters are applicable and get applied by the server. +// See https://godoc.org/google.golang.org/grpc/keepalive#ServerParameters for details. +type KeepaliveServerParameters struct { + MaxConnectionIdle time.Duration `mapstructure:"max_connection_idle,omitempty"` + MaxConnectionAge time.Duration `mapstructure:"max_connection_age,omitempty"` + MaxConnectionAgeGrace time.Duration `mapstructure:"max_connection_age_grace,omitempty"` + Time time.Duration `mapstructure:"time,omitempty"` + Timeout time.Duration `mapstructure:"timeout,omitempty"` +} + +// KeepaliveEnforcementPolicy allow configuration of the keepalive.EnforcementPolicy. +// The same default values as keepalive.EnforcementPolicy are applicable and get applied by the server. +// See https://godoc.org/google.golang.org/grpc/keepalive#EnforcementPolicy for details. +type KeepaliveEnforcementPolicy struct { + MinTime time.Duration `mapstructure:"min_time,omitempty"` + PermitWithoutStream bool `mapstructure:"permit_without_stream,omitempty"` +} + +type GRPCServerSettings struct { + // Server net.Addr config. For transport only "tcp" and "unix" are valid options. + NetAddr confignet.NetAddr `mapstructure:",squash"` + + // Configures the protocol to use TLS. + // The default value is nil, which will cause the protocol to not use TLS. + TLSSetting *configtls.TLSServerSetting `mapstructure:"tls_settings,omitempty"` + + // MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server. + MaxRecvMsgSizeMiB uint64 `mapstructure:"max_recv_msg_size_mib"` + + // MaxConcurrentStreams sets the limit on the number of concurrent streams to each ServerTransport. + // It has effect only for streaming RPCs. + MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams"` + + // ReadBufferSize for gRPC server. See grpc.ReadBufferSize + // (https://godoc.org/google.golang.org/grpc#ReadBufferSize). + ReadBufferSize int `mapstructure:"read_buffer_size"` + + // WriteBufferSize for gRPC server. See grpc.WriteBufferSize + // (https://godoc.org/google.golang.org/grpc#WriteBufferSize). + WriteBufferSize int `mapstructure:"write_buffer_size"` + + // Keepalive anchor for all the settings related to keepalive. + Keepalive *KeepaliveServerConfig `mapstructure:"keepalive,omitempty"` + + // Auth for this receiver + Auth *configauth.Authentication `mapstructure:"auth,omitempty"` +} + +// ToDialOptions maps configgrpc.GRPCClientSettings to a slice of dial options for gRPC +func (gcs *GRPCClientSettings) ToDialOptions() ([]grpc.DialOption, error) { + var opts []grpc.DialOption + if gcs.Compression != "" { + if compressionKey := GetGRPCCompressionKey(gcs.Compression); compressionKey != CompressionUnsupported { + opts = append(opts, grpc.WithDefaultCallOptions(grpc.UseCompressor(compressionKey))) + } else { + return nil, fmt.Errorf("unsupported compression type %q", gcs.Compression) + } + } + + tlsCfg, err := gcs.TLSSetting.LoadTLSConfig() + if err != nil { + return nil, err + } + tlsDialOption := grpc.WithInsecure() + if tlsCfg != nil { + tlsDialOption = grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)) + } + opts = append(opts, tlsDialOption) + + if gcs.ReadBufferSize > 0 { + opts = append(opts, grpc.WithReadBufferSize(gcs.ReadBufferSize)) + } + + if gcs.WriteBufferSize > 0 { + opts = append(opts, grpc.WithWriteBufferSize(gcs.WriteBufferSize)) + } + + if gcs.Keepalive != nil { + keepAliveOption := grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: gcs.Keepalive.Time, + Timeout: gcs.Keepalive.Timeout, + PermitWithoutStream: gcs.Keepalive.PermitWithoutStream, + }) + opts = append(opts, keepAliveOption) + } + + if gcs.PerRPCAuth != nil { + if strings.EqualFold(gcs.PerRPCAuth.AuthType, PerRPCAuthTypeBearer) { + sToken := gcs.PerRPCAuth.BearerToken + token := BearerToken(sToken) + opts = append(opts, grpc.WithPerRPCCredentials(token)) + } else { + return nil, fmt.Errorf("unsupported per-RPC auth type %q", gcs.PerRPCAuth.AuthType) + } + } + + if gcs.BalancerName != "" { + valid := validateBalancerName(gcs.BalancerName) + if !valid { + return nil, fmt.Errorf("invalid balancer_name: %s", gcs.BalancerName) + } + opts = append(opts, grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, gcs.BalancerName))) + } + + return opts, nil +} + +func validateBalancerName(balancerName string) bool { + for _, item := range allowedBalancerNames { + if item == balancerName { + return true + } + } + return false +} + +func (gss *GRPCServerSettings) ToListener() (net.Listener, error) { + return gss.NetAddr.Listen() +} + +// ToServerOption maps configgrpc.GRPCServerSettings to a slice of server options for gRPC +func (gss *GRPCServerSettings) ToServerOption() ([]grpc.ServerOption, error) { + var opts []grpc.ServerOption + + if gss.TLSSetting != nil { + tlsCfg, err := gss.TLSSetting.LoadTLSConfig() + if err != nil { + return nil, err + } + opts = append(opts, grpc.Creds(credentials.NewTLS(tlsCfg))) + } + + if gss.MaxRecvMsgSizeMiB > 0 { + opts = append(opts, grpc.MaxRecvMsgSize(int(gss.MaxRecvMsgSizeMiB*1024*1024))) + } + + if gss.MaxConcurrentStreams > 0 { + opts = append(opts, grpc.MaxConcurrentStreams(gss.MaxConcurrentStreams)) + } + + if gss.ReadBufferSize > 0 { + opts = append(opts, grpc.ReadBufferSize(gss.ReadBufferSize)) + } + + if gss.WriteBufferSize > 0 { + opts = append(opts, grpc.WriteBufferSize(gss.WriteBufferSize)) + } + + // The default values referenced in the GRPC docs are set within the server, so this code doesn't need + // to apply them over zero/nil values before passing these as grpc.ServerOptions. + // The following shows the server code for applying default grpc.ServerOptions. + // https://github.com/grpc/grpc-go/blob/120728e1f775e40a2a764341939b78d666b08260/internal/transport/http2_server.go#L184-L200 + if gss.Keepalive != nil { + if gss.Keepalive.ServerParameters != nil { + svrParams := gss.Keepalive.ServerParameters + opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionIdle: svrParams.MaxConnectionIdle, + MaxConnectionAge: svrParams.MaxConnectionAge, + MaxConnectionAgeGrace: svrParams.MaxConnectionAgeGrace, + Time: svrParams.Time, + Timeout: svrParams.Timeout, + })) + } + // The default values referenced in the GRPC are set within the server, so this code doesn't need + // to apply them over zero/nil values before passing these as grpc.ServerOptions. + // The following shows the server code for applying default grpc.ServerOptions. + // https://github.com/grpc/grpc-go/blob/120728e1f775e40a2a764341939b78d666b08260/internal/transport/http2_server.go#L202-L205 + if gss.Keepalive.EnforcementPolicy != nil { + enfPol := gss.Keepalive.EnforcementPolicy + opts = append(opts, grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ + MinTime: enfPol.MinTime, + PermitWithoutStream: enfPol.PermitWithoutStream, + })) + } + } + + if gss.Auth != nil { + authOpts, err := gss.Auth.ToServerOptions() + if err != nil { + return nil, err + } + opts = append(opts, authOpts...) + } + + return opts, nil +} + +// GetGRPCCompressionKey returns the grpc registered compression key if the +// passed in compression key is supported, and CompressionUnsupported otherwise +func GetGRPCCompressionKey(compressionType string) string { + compressionKey := strings.ToLower(compressionType) + if encodingKey, ok := grpcCompressionKeyMap[compressionKey]; ok { + return encodingKey + } + return CompressionUnsupported +} diff --git a/internal/otel_collector/config/configgrpc/configgrpc_test.go b/internal/otel_collector/config/configgrpc/configgrpc_test.go new file mode 100644 index 00000000000..035176fd6a2 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/configgrpc_test.go @@ -0,0 +1,523 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configgrpc + +import ( + "context" + "path" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/config/configauth" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtls" + otelcol "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + "go.opentelemetry.io/collector/testutil" +) + +func TestDefaultGrpcClientSettings(t *testing.T) { + gcs := &GRPCClientSettings{ + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + opts, err := gcs.ToDialOptions() + assert.NoError(t, err) + assert.Len(t, opts, 1) +} + +func TestAllGrpcClientSettings(t *testing.T) { + gcs := &GRPCClientSettings{ + Headers: map[string]string{ + "test": "test", + }, + Endpoint: "localhost:1234", + Compression: "gzip", + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + Keepalive: &KeepaliveClientConfig{ + Time: time.Second, + Timeout: time.Second, + PermitWithoutStream: true, + }, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + WaitForReady: true, + PerRPCAuth: nil, + BalancerName: "round_robin", + } + opts, err := gcs.ToDialOptions() + assert.NoError(t, err) + assert.Len(t, opts, 6) +} + +func TestDefaultGrpcServerSettings(t *testing.T) { + gss := &GRPCServerSettings{} + opts, err := gss.ToServerOption() + assert.NoError(t, err) + assert.Len(t, opts, 0) +} + +func TestAllGrpcServerSettingsExceptAuth(t *testing.T) { + gss := &GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:1234", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{}, + ClientCAFile: "", + }, + MaxRecvMsgSizeMiB: 1, + MaxConcurrentStreams: 1024, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + Keepalive: &KeepaliveServerConfig{ + ServerParameters: &KeepaliveServerParameters{ + MaxConnectionIdle: time.Second, + MaxConnectionAge: time.Second, + MaxConnectionAgeGrace: time.Second, + Time: time.Second, + Timeout: time.Second, + }, + EnforcementPolicy: &KeepaliveEnforcementPolicy{ + MinTime: time.Second, + PermitWithoutStream: true, + }, + }, + } + opts, err := gss.ToServerOption() + assert.NoError(t, err) + assert.Len(t, opts, 7) +} + +func TestGrpcServerAuthSettings(t *testing.T) { + gss := &GRPCServerSettings{} + + // sanity check + _, err := gss.ToServerOption() + require.NoError(t, err) + + // test + gss.Auth = &configauth.Authentication{ + OIDC: &configauth.OIDC{}, + } + opts, err := gss.ToServerOption() + + // verify + // an error here is a positive confirmation that Auth kicked in + assert.Error(t, err) + assert.Nil(t, opts) +} + +func TestGRPCClientSettingsError(t *testing.T) { + tests := []struct { + settings GRPCClientSettings + err string + }{ + { + err: "^failed to load TLS config: failed to load CA CertPool: failed to load CA /doesnt/exist:", + settings: GRPCClientSettings{ + Headers: nil, + Endpoint: "", + Compression: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/doesnt/exist", + }, + Insecure: false, + ServerName: "", + }, + Keepalive: nil, + }, + }, + { + err: "^failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither", + settings: GRPCClientSettings{ + Headers: nil, + Endpoint: "", + Compression: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "/doesnt/exist", + }, + Insecure: false, + ServerName: "", + }, + Keepalive: nil, + }, + }, + { + err: "invalid balancer_name: test", + settings: GRPCClientSettings{ + Headers: map[string]string{ + "test": "test", + }, + Endpoint: "localhost:1234", + Compression: "gzip", + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + Keepalive: &KeepaliveClientConfig{ + Time: time.Second, + Timeout: time.Second, + PermitWithoutStream: true, + }, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + WaitForReady: true, + BalancerName: "test", + }, + }, + } + for _, test := range tests { + t.Run(test.err, func(t *testing.T) { + opts, err := test.settings.ToDialOptions() + assert.Nil(t, opts) + assert.Error(t, err) + assert.Regexp(t, test.err, err) + }) + } +} + +func TestUseSecure(t *testing.T) { + gcs := &GRPCClientSettings{ + Headers: nil, + Endpoint: "", + Compression: "", + TLSSetting: configtls.TLSClientSetting{}, + Keepalive: nil, + PerRPCAuth: nil, + } + dialOpts, err := gcs.ToDialOptions() + assert.NoError(t, err) + assert.Equal(t, len(dialOpts), 1) +} + +func TestGRPCServerSettingsError(t *testing.T) { + tests := []struct { + settings GRPCServerSettings + err string + }{ + { + err: "^failed to load TLS config: failed to load CA CertPool: failed to load CA /doesnt/exist:", + settings: GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "127.0.0.1:1234", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/doesnt/exist", + }, + }, + }, + }, + { + err: "^failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither", + settings: GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "127.0.0.1:1234", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "/doesnt/exist", + }, + }, + }, + }, + { + err: "^failed to load TLS config: failed to load client CA CertPool: failed to load CA /doesnt/exist:", + settings: GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "127.0.0.1:1234", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + ClientCAFile: "/doesnt/exist", + }, + }, + }, + } + for _, test := range tests { + t.Run(test.err, func(t *testing.T) { + _, err := test.settings.ToServerOption() + assert.Regexp(t, test.err, err) + }) + } +} + +func TestGRPCServerSettings_ToListener_Error(t *testing.T) { + settings := GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "127.0.0.1:1234567", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "/doesnt/exist", + }, + }, + Keepalive: nil, + } + _, err := settings.ToListener() + assert.Error(t, err) +} + +func TestGetGRPCCompressionKey(t *testing.T) { + if GetGRPCCompressionKey("gzip") != CompressionGzip { + t.Error("gzip is marked as supported but returned unsupported") + } + + if GetGRPCCompressionKey("Gzip") != CompressionGzip { + t.Error("Capitalization of CompressionGzip should not matter") + } + + if GetGRPCCompressionKey("badType") != CompressionUnsupported { + t.Error("badType is not supported but was returned as supported") + } +} + +func TestHttpReception(t *testing.T) { + tests := []struct { + name string + tlsServerCreds *configtls.TLSServerSetting + tlsClientCreds *configtls.TLSClientSetting + hasError bool + }{ + { + name: "noTLS", + tlsServerCreds: nil, + tlsClientCreds: &configtls.TLSClientSetting{ + Insecure: true, + }, + }, + { + name: "TLS", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + ServerName: "localhost", + }, + }, + { + name: "NoServerCertificates", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + ServerName: "localhost", + }, + hasError: true, + }, + { + name: "mTLS", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + ClientCAFile: path.Join(".", "testdata", "ca.crt"), + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "client.crt"), + KeyFile: path.Join(".", "testdata", "client.key"), + }, + ServerName: "localhost", + }, + }, + { + name: "NoClientCertificate", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + ClientCAFile: path.Join(".", "testdata", "ca.crt"), + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + ServerName: "localhost", + }, + hasError: true, + }, + { + name: "WrongClientCA", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + ClientCAFile: path.Join(".", "testdata", "server.crt"), + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "client.crt"), + KeyFile: path.Join(".", "testdata", "client.key"), + }, + ServerName: "localhost", + }, + hasError: true, + }, + } + // prepare + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gss := &GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:0", + Transport: "tcp", + }, + TLSSetting: tt.tlsServerCreds, + } + ln, err := gss.ToListener() + assert.NoError(t, err) + opts, err := gss.ToServerOption() + assert.NoError(t, err) + s := grpc.NewServer(opts...) + otelcol.RegisterTraceServiceServer(s, &grpcTraceServer{}) + + go func() { + _ = s.Serve(ln) + }() + + gcs := &GRPCClientSettings{ + Endpoint: ln.Addr().String(), + TLSSetting: *tt.tlsClientCreds, + } + clientOpts, errClient := gcs.ToDialOptions() + assert.NoError(t, errClient) + grpcClientConn, errDial := grpc.Dial(gcs.Endpoint, clientOpts...) + assert.NoError(t, errDial) + client := otelcol.NewTraceServiceClient(grpcClientConn) + ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second) + resp, errResp := client.Export(ctx, &otelcol.ExportTraceServiceRequest{}, grpc.WaitForReady(true)) + if tt.hasError { + assert.Error(t, errResp) + } else { + assert.NoError(t, errResp) + assert.NotNil(t, resp) + } + cancelFunc() + s.Stop() + }) + } +} + +func TestReceiveOnUnixDomainSocket(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + socketName := testutil.TempSocketName(t) + gss := &GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: socketName, + Transport: "unix", + }, + } + ln, err := gss.ToListener() + assert.NoError(t, err) + opts, err := gss.ToServerOption() + assert.NoError(t, err) + s := grpc.NewServer(opts...) + otelcol.RegisterTraceServiceServer(s, &grpcTraceServer{}) + + go func() { + _ = s.Serve(ln) + }() + + gcs := &GRPCClientSettings{ + Endpoint: "unix://" + ln.Addr().String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + clientOpts, errClient := gcs.ToDialOptions() + assert.NoError(t, errClient) + grpcClientConn, errDial := grpc.Dial(gcs.Endpoint, clientOpts...) + assert.NoError(t, errDial) + client := otelcol.NewTraceServiceClient(grpcClientConn) + ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second) + resp, errResp := client.Export(ctx, &otelcol.ExportTraceServiceRequest{}, grpc.WaitForReady(true)) + assert.NoError(t, errResp) + assert.NotNil(t, resp) + cancelFunc() + s.Stop() +} + +type grpcTraceServer struct{} + +func (gts *grpcTraceServer) Export(context.Context, *otelcol.ExportTraceServiceRequest) (*otelcol.ExportTraceServiceResponse, error) { + return &otelcol.ExportTraceServiceResponse{}, nil +} + +func TestWithPerRPCAuthBearerToken(t *testing.T) { + // prepare + // test + gcs := &GRPCClientSettings{ + PerRPCAuth: &PerRPCAuthConfig{ + AuthType: "bearer", + BearerToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + }, + } + dialOpts, err := gcs.ToDialOptions() + + // verify + assert.NoError(t, err) + assert.Len(t, dialOpts, 2) // WithInsecure and WithPerRPCCredentials +} + +func TestWithPerRPCAuthInvalidAuthType(t *testing.T) { + // test + gcs := &GRPCClientSettings{ + PerRPCAuth: &PerRPCAuthConfig{ + AuthType: "non-existing", + }, + } + dialOpts, err := gcs.ToDialOptions() + + // verify + assert.Error(t, err) + assert.Nil(t, dialOpts) +} diff --git a/internal/otel_collector/config/configgrpc/gzip.go b/internal/otel_collector/config/configgrpc/gzip.go new file mode 100644 index 00000000000..68f9d98617e --- /dev/null +++ b/internal/otel_collector/config/configgrpc/gzip.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configgrpc + +import ( + // import the gzip package with auto-registers the gzip grpc compressor + _ "google.golang.org/grpc/encoding/gzip" +) diff --git a/internal/otel_collector/config/configgrpc/testdata/ca.crt b/internal/otel_collector/config/configgrpc/testdata/ca.crt new file mode 100644 index 00000000000..7a677e39a77 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/testdata/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CCQCYMh590xiOGzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB +VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM +CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIwMDkyMjA1MjIx +MFoXDTMwMDkyMDA1MjIxMFowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry +YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV +BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AJdLtbEbPVuBQqh2MLDuyYB/zg3dMfl1jyE64UgW5AVRHrfAGXgP55yeEh3XGiO+ +i5PYNeEILoYcLXtMstA24OTgxeLjTZ0zEaja50/Ow9/NjZcTc0f/DErHI3GvWTxW +dCdosGe4qSwi9BbRGPfAat5fJMSTERXDcAcH2aaD3ekK3WTqtXsFsErF7+SpzJfL +PZw4aSFS9a26PkxO+Z5coqdYRC1CIpZGVFRg/PVcb7NNTrRf+Wu/hOncNkHDXKKz +qeBkhnHczQrPDzxhG2FvrahMgGSsRgBDdMwTBfmBhlbP+sM0HSPuCmKD95/osO03 +sG13nWMSDb7QYETVyg3E4t8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfLGe9cAN +1WH09KVYIWWzu74tkaOIRFdkXzcx6fMq4Gpi49/lxG1INCrJ/4F8UyhHq0mmSsxb +UGs3KFfDsRctX7PNgOLYHxlUcAhQFzT3xqrRg7iqaiGWKTSGE1fXg29LKm/Ox/MC +npumt7rsSix5Viyb0/njcSX8CdSCirhKCiJklfd5J/Cwxqm+j/Pgaz2YrOj8Axa1 +/GJtPOtIpPYEBbXXUMpuijSikcfurZJL62WWxrzUGZjRsmSJAl5bvTJTOKGQb634 +Y0oehROKnkA2N0UVa4LM2M5C+CVZNl8vKAsdj1pywRGEOQoH42wBNu71Wob1f7jt +JOXWGJcoyEjbSg== +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/configgrpc/testdata/client.crt b/internal/otel_collector/config/configgrpc/testdata/client.crt new file mode 100644 index 00000000000..2fc037de49f --- /dev/null +++ b/internal/otel_collector/config/configgrpc/testdata/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdZMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjEwWhcNMzAwOTIwMDUyMjEwWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAyMraDxgfr7DEShaRbpKnKnRm4xrh9StCpTWVxViCS4JmACrlNsDBggan +Xz4rQSsV1Z2lznYPdbpXVVDY/8Q87GDXQLmB48cff+DLdU2TAvsalraty4edlf1Q +j6WNi/jFca9XIqqS358bmBau3SlEEJVv0StE8fDiZpHQuYADtdXxWhXGcrNC3quu +GKBtTCaj01EiZU5Rdqzd/KFEUQ5ns5K8j1vXJJzEhbmOXRN4NM0vvEBnd3ObP+Lw +pFUSkhxgYYLga8L5432bg/BA7OSLhZoEZzuMivyyNVC7sIoyLBYR0/Nk53ICmKz4 +gR18lTmpDXnmFZv7D1HXhwvFQ/xvbwIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAM +K90gY676JVzErSogCDn3XCsEkSLyBa90LK2FMogU7ZF0x2/Y6qf2yBLqYAjp4Br1 +CXmmQAXyLGsTs1ahobgZNVEvdvhVxc6CHBw4aBbXaUfVGq26xauPu47wWHtxEeAx +h9huRcZZKtTsJBJ1N6Yg7mJbzFT0nQ0FGWuoWd9HQP7ncOlfmlBfuAGRKRn1lXXr +na0thmOFQskzyAByijuGuaFvr+v4IVHYqO3JPXNpwp2LNHvD/f0OOS2XWpsUX6Vn +2IDdMgZSNLrHDZpemtl1QSaHemG8s67LEvuG0/fsfV38pKPlhKV1xrkojNN3kvPq +IyU5uT3m01KkJAMtRrMT +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/configgrpc/testdata/client.key b/internal/otel_collector/config/configgrpc/testdata/client.key new file mode 100644 index 00000000000..4c77070cc03 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/testdata/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyMraDxgfr7DEShaRbpKnKnRm4xrh9StCpTWVxViCS4JmACrl +NsDBgganXz4rQSsV1Z2lznYPdbpXVVDY/8Q87GDXQLmB48cff+DLdU2TAvsalrat +y4edlf1Qj6WNi/jFca9XIqqS358bmBau3SlEEJVv0StE8fDiZpHQuYADtdXxWhXG +crNC3quuGKBtTCaj01EiZU5Rdqzd/KFEUQ5ns5K8j1vXJJzEhbmOXRN4NM0vvEBn +d3ObP+LwpFUSkhxgYYLga8L5432bg/BA7OSLhZoEZzuMivyyNVC7sIoyLBYR0/Nk +53ICmKz4gR18lTmpDXnmFZv7D1HXhwvFQ/xvbwIDAQABAoIBADHS1AUG0WYBENPp +gbDURxKry5Py6bqyP1lLUJyld79Q3gqQmkvZzKp9CC8D+Cu1izd0ZN40QWXPFTig +VRgyE4P8C62N2oMwt8o9d37l/uKweEqJjdqBDkNXlhPu2o6u7h9liNObS9KdYnV8 +u2s5gCA1VIesmvEF+sfEyuwcrc8ClHf4qs7VDqopZ6HZ3aT5ns4xXA5QoEZJlhDG +axwqWQ/jC4G+nGyrE2/AAGAgQtRhcs8aHTuEGBlNGlC9af/obyYLCqPm0A6ceyKz +PcZUDQCrsZnQpwqF7zsF7WmW8W5XqVHDFoJaNQt2/sp3OkOv9z78JodvB/MbGmNV +MkP1GeECgYEA9kbhLVsDDPA82wQuBsbK9u6A59ZPIXDfXJVNjcg1LKJkqJsKhY9z +uZ98rHlTI+FS5sCL/ixdM/tVNFI3EHaS7wOLJI9y2y+CVi2d5ffMKbPUtFJf5Q+A +zlJq1LseKdwsVT1jSah/jZ53YW1pOiJZPByUfLWIwLNHo0+fIEMfCTkCgYEA0LhC +sNb1W8GpMy6eTDfa4D90Wm0LvZgEyV8SCc09xdC6yp1bE4G19x037/YQsbLnE5vB +0YM8ILh977zCYHokC9qMdKDAxZx0RQD2IUDRbTymQ89uS5ednSg9dBxs9f/cxTlU +wQUxf4+yY/Rohyo0+mK4zkobG9lU1H83KKc1BecCgYEAkvQkdW3zWgsYJRBPbpe8 +kLAslypYOXoirhohFtM6d5HHQpyRILVCtqamPDyBEc3oK+0FG/vY+aWlZ/0PAnHe +p2ST6JL4VDX7LfU2XP0KBHBcIeVtdz9S+spPGPU2wH+yrIJe9prm0diXH7mrqpbI +bIgZSnkASwwvWRGvwA6NPHECgYBkD+JRG0zXp3lxgyj6y1BQb7tdWqflRgsNa1mf +f1jdDBtw5Y1zRZ0yEjzt+p64QleLzAFYaz0ZRrmBhJH/ZK8BS85IX4Trd/0506Ms +AAInB4uCOODctpwmatNDZhlKulZh6wFZ5B591CsmxlaSbkalv0xwAZELgd6sXSzZ +fYfrAwKBgQDM9StAiTdSjGn0Qk/YzkLlloEEebjJ7tRUpDGQgX3Z7YsCdfl/LeWU +yMV7UVDggPVjveT8TUJUm+ipD7CpesY1GTJovyRWKlyMpgAY2wKXV41oOnDD/0ef +AAa3FWMAf27ogbeXBxSUBN+1EhKBMKihQSD+Odnbu6SHUeiKskGU3Q== +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/config/configgrpc/testdata/server.crt b/internal/otel_collector/config/configgrpc/testdata/server.crt new file mode 100644 index 00000000000..70292eae048 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/testdata/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdYMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjEwWhcNMzAwOTIwMDUyMjEwWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA0HqHHTXVljLlyknGttODUu675uGGJJWdUCmjr+I9+3BAxGgNG4xVjMao +IsgAHajXgfTgfLha0mnMCdAHL4OUmvAj7sJ6NZRIK9DASiu5NWLQhgjUDg1DY2K/ +nhCVGp3w/J6aFfN+qSIaHCQz7MN66mcPtsXxMMPUBlqjWCLFpR5vL2M6k5tZI1L2 +pP4jfkfEIdlHgs/AXftwiYsNo57Rlaj+O7DwPqmJdVGeeE6Wka4ANK/5svIAgW9h +mwKhSwaXwle8GDMYgtbfQCrIO/Z5ctMKG9KXEgOBpoANYmeAGT2OPaCc43710T9P +MONdj3TKc8Y5FsUA0/kwac7oGFl+hQIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCP +5as7sDX2vcXbROg62Weyg6YCDd12LYRWS2UHBebxwDFKVj1GmkJM2eN5bZWlyULI +Sv0yXSIQuMXIZDECdg0+YWxk6ZV57iYkduhez61wqhqYu9H1h5jlOvdlevunfNZ3 +VlpIkE2vVRIpu+IiNRSkh08M5csAe7MsrgdUcgenjygwNM3wPaQtlQ7tZ+quWyYc +rHO2lByVexHwpN2ZPiMZ7eIyEs9W2kt6ohcr8jJdryfO+7Q2FR5vE8K1Uh1wNcFh +WLPMIl4InYmIFfUChHvHCEmLS0TLW4lD9srFmO7VrlrPqUOULzUIm5wuXWgvdxw9 +3XHsXLqvMOf79boGpkfv +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/configgrpc/testdata/server.key b/internal/otel_collector/config/configgrpc/testdata/server.key new file mode 100644 index 00000000000..98fba4b9432 --- /dev/null +++ b/internal/otel_collector/config/configgrpc/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0HqHHTXVljLlyknGttODUu675uGGJJWdUCmjr+I9+3BAxGgN +G4xVjMaoIsgAHajXgfTgfLha0mnMCdAHL4OUmvAj7sJ6NZRIK9DASiu5NWLQhgjU +Dg1DY2K/nhCVGp3w/J6aFfN+qSIaHCQz7MN66mcPtsXxMMPUBlqjWCLFpR5vL2M6 +k5tZI1L2pP4jfkfEIdlHgs/AXftwiYsNo57Rlaj+O7DwPqmJdVGeeE6Wka4ANK/5 +svIAgW9hmwKhSwaXwle8GDMYgtbfQCrIO/Z5ctMKG9KXEgOBpoANYmeAGT2OPaCc +43710T9PMONdj3TKc8Y5FsUA0/kwac7oGFl+hQIDAQABAoIBAFTJD/wcMcIE7xlG +yc7+1FC9EKQEIgbs5e59ELnuG+EPNPfrjTEf8IbxH94NUqa9TO/oRAfU/fLG3hk7 +hkCXla8xbJukcgkqRfOz0RAZGhiRGFb6bitMz5Qyy9Ufz1Pk2eYTJn046tEkMlQx +kQCAO5Pq2CQv+jgn3Cm9YOLuOU0+CEpET2lNgdUbj7wo0k2jDbxuU/CGqrQua8uH +hwM2hBH3eZJzO7EwdBhdubImg9RsDrLUkltgdVMAROP5+m03+J653v4UbaAvG4jM +IxkVW11Wdh4caKJNQY5gnNhnNG79uDeikX/dLTnSnvaARQWay/XKhP9EyKp3zVVk +4S4GEyECgYEA8TjZiGLSbgERoigUpgMxECyUAi/+GffiAR4PARdaoCXLGlXYBAax +N8n+CF62VS7FY0IYeGuzDG9O/rEGS27OBVM2j+cCkqOT9+vNJe5iwGfAzwuBAuCA +m5eRysLG4jIwhw2XRCL2gGQM92XodKkShAhXuG05nUqcUdpgdpBdJK0CgYEA3UAn +YhbXvNKUHcpjvyyQpLrJHS+Z+key1e8FWJ8TzQDWldbgCeJZrm9xCNARKZEXFxNG +V3MJWehKl2mC8ctU6u1aTi83bA7MdVvDw57SGj0HMLa4CXtdWEOt57Z6HzFJLoQy +aAxvKwbeBfyRbt7f5HaHw/w3VjZN9HA7ip7EJDkCgYAFlWhLpOX0D+hFlaHsudQv +6KhAaLX8CeXcWsLEJrM9U8KgyG3oofMGNJHBxdd4n02IX6ZLW0rYtdbhRF2970Gr +k+KGcDV6CXlKWtXz09HLXFt1L3H8DBBOCbMhO2L5J2pCJgljVV/ZVveJ3n0D/knk +boEBTt3viyOVLXXgKLVPPQKBgEFtGzhScPGRg+NbWivKTeuooJhU3z+3vBavW/Fc ++UoCGXKt3AqQONzwb4ifnrOgCCf2tzJc/kLsAkLMHMDL1Ay0q6O7KrR1m9iIjldm +u9KugVXScpG7PVtAiEihGXPn6zAqP42tP6KFoVo72fXjSmoQ8wztpJ+F53+FQNY5 +JN9hAoGBALV/knUA5tVyMofCoZflHVM3E4pFJiKQII1dzxE4g/sLYtSWDfKXP64W +mc7PKy46vjeTeRE0B8sGQ7RIhnUpPaA6OS8sMPSeJtdvfAPK0KCukHXTsLJg5DZo +XuC2gsdqPFQJQ/VDnp3JO7rbj7A3uYgzRT5xKHMluJnDDuUg1UUr +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/config/confighttp/README.md b/internal/otel_collector/config/confighttp/README.md new file mode 100644 index 00000000000..5449b679333 --- /dev/null +++ b/internal/otel_collector/config/confighttp/README.md @@ -0,0 +1,56 @@ +# HTTP Configuration Settings + +HTTP exposes a [variety of settings](https://golang.org/pkg/net/http/). +Several of these settings are available for configuration within individual +receivers or exporters. + +## Client Configuration + +[Exporters](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/README.md) +leverage client configuration. + +Note that client configuration supports TLS configuration, however +configuration parameters are not defined under `tls_settings` like server +configuration. For more information, see [configtls +README](../configtls/README.md). + +- `endpoint`: address:port +- `headers`: name/value pairs added to the HTTP request headers +- [`read_buffer_size`](https://golang.org/pkg/net/http/#Transport) +- [`timeout`](https://golang.org/pkg/net/http/#Client) +- [`write_buffer_size`](https://golang.org/pkg/net/http/#Transport) + +Example: + +```yaml +exporter: + otlp: + endpoint: otelcol2:55690 + headers: + test1: "value1" + "test 2": "value 2" +``` + +## Server Configuration + +[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/master/receiver/README.md) +leverage server configuration. + +- [`cors_allowed_origins`](https://github.com/rs/cors): An empty list means + that CORS is not enabled at all. A wildcard can be used to match any origin + or one or more characters of an origin. +- `endpoint`: Valid value syntax available [here](https://github.com/grpc/grpc/blob/master/doc/naming.md) +- [`tls_settings`](../configtls/README.md) + +Example: + +```yaml +receivers: + otlp: + cors_allowed_origins: + - https://foo.bar.com + - https://*.test.com + endpoint: 0.0.0.0:55690 + protocols: + http: +``` diff --git a/internal/otel_collector/config/confighttp/confighttp.go b/internal/otel_collector/config/confighttp/confighttp.go new file mode 100644 index 00000000000..56936c74da8 --- /dev/null +++ b/internal/otel_collector/config/confighttp/confighttp.go @@ -0,0 +1,168 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package confighttp + +import ( + "crypto/tls" + "net" + "net/http" + "time" + + "github.com/rs/cors" + + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/internal/middleware" +) + +type HTTPClientSettings struct { + // The target URL to send data to (e.g.: http://some.url:9411/v1/traces). + Endpoint string `mapstructure:"endpoint"` + + // TLSSetting struct exposes TLS client configuration. + TLSSetting configtls.TLSClientSetting `mapstructure:",squash"` + + // ReadBufferSize for HTTP client. See http.Transport.ReadBufferSize. + ReadBufferSize int `mapstructure:"read_buffer_size"` + + // WriteBufferSize for HTTP client. See http.Transport.WriteBufferSize. + WriteBufferSize int `mapstructure:"write_buffer_size"` + + // Timeout parameter configures `http.Client.Timeout`. + Timeout time.Duration `mapstructure:"timeout,omitempty"` + + // Additional headers attached to each HTTP request sent by the client. + // Existing header values are overwritten if collision happens. + Headers map[string]string `mapstructure:"headers,omitempty"` + + // Custom Round Tripper to allow for individual components to intercept HTTP requests + CustomRoundTripper func(next http.RoundTripper) (http.RoundTripper, error) +} + +func (hcs *HTTPClientSettings) ToClient() (*http.Client, error) { + tlsCfg, err := hcs.TLSSetting.LoadTLSConfig() + if err != nil { + return nil, err + } + transport := http.DefaultTransport.(*http.Transport).Clone() + if tlsCfg != nil { + transport.TLSClientConfig = tlsCfg + } + if hcs.ReadBufferSize > 0 { + transport.ReadBufferSize = hcs.ReadBufferSize + } + if hcs.WriteBufferSize > 0 { + transport.WriteBufferSize = hcs.WriteBufferSize + } + + clientTransport := (http.RoundTripper)(transport) + if len(hcs.Headers) > 0 { + clientTransport = &headerRoundTripper{ + transport: transport, + headers: hcs.Headers, + } + } + + if hcs.CustomRoundTripper != nil { + clientTransport, err = hcs.CustomRoundTripper(clientTransport) + if err != nil { + return nil, err + } + } + + return &http.Client{ + Transport: clientTransport, + Timeout: hcs.Timeout, + }, nil +} + +// Custom RoundTripper that add headers +type headerRoundTripper struct { + transport http.RoundTripper + headers map[string]string +} + +// Custom RoundTrip that add headers +func (interceptor *headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + for k, v := range interceptor.headers { + req.Header.Set(k, v) + } + // Send the request to next transport. + return interceptor.transport.RoundTrip(req) +} + +type HTTPServerSettings struct { + // Endpoint configures the listening address for the server. + Endpoint string `mapstructure:"endpoint"` + + // TLSSetting struct exposes TLS client configuration. + TLSSetting *configtls.TLSServerSetting `mapstructure:"tls_settings, omitempty"` + + // CorsOrigins are the allowed CORS origins for HTTP/JSON requests to grpc-gateway adapter + // for the OTLP receiver. See github.com/rs/cors + // An empty list means that CORS is not enabled at all. A wildcard (*) can be + // used to match any origin or one or more characters of an origin. + CorsOrigins []string `mapstructure:"cors_allowed_origins"` +} + +func (hss *HTTPServerSettings) ToListener() (net.Listener, error) { + listener, err := net.Listen("tcp", hss.Endpoint) + if err != nil { + return nil, err + } + + if hss.TLSSetting != nil { + var tlsCfg *tls.Config + tlsCfg, err = hss.TLSSetting.LoadTLSConfig() + if err != nil { + return nil, err + } + listener = tls.NewListener(listener, tlsCfg) + } + return listener, nil +} + +// toServerOptions has options that change the behavior of the HTTP server +// returned by HTTPServerSettings.ToServer(). +type toServerOptions struct { + errorHandler middleware.ErrorHandler +} + +type ToServerOption func(opts *toServerOptions) + +// WithErrorHandler overrides the HTTP error handler that gets invoked +// when there is a failure inside middleware.HTTPContentDecompressor. +func WithErrorHandler(e middleware.ErrorHandler) ToServerOption { + return func(opts *toServerOptions) { + opts.errorHandler = e + } +} + +func (hss *HTTPServerSettings) ToServer(handler http.Handler, opts ...ToServerOption) *http.Server { + serverOpts := &toServerOptions{} + for _, o := range opts { + o(serverOpts) + } + if len(hss.CorsOrigins) > 0 { + co := cors.Options{AllowedOrigins: hss.CorsOrigins} + handler = cors.New(co).Handler(handler) + } + handler = middleware.HTTPContentDecompressor( + handler, + middleware.WithErrorHandler(serverOpts.errorHandler), + ) + return &http.Server{ + Handler: handler, + } +} diff --git a/internal/otel_collector/config/confighttp/confighttp_test.go b/internal/otel_collector/config/confighttp/confighttp_test.go new file mode 100644 index 00000000000..66d98e4925a --- /dev/null +++ b/internal/otel_collector/config/confighttp/confighttp_test.go @@ -0,0 +1,427 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package confighttp + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configtls" +) + +func TestAllHTTPClientSettings(t *testing.T) { + tests := []struct { + name string + settings HTTPClientSettings + shouldError bool + }{ + { + name: "all_valid_settings", + settings: HTTPClientSettings{ + Endpoint: "localhost:1234", + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + ReadBufferSize: 1024, + WriteBufferSize: 512, + CustomRoundTripper: func(next http.RoundTripper) (http.RoundTripper, error) { return next, nil }, + }, + shouldError: false, + }, + { + name: "error_round_tripper_returned", + settings: HTTPClientSettings{ + Endpoint: "localhost:1234", + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + ReadBufferSize: 1024, + WriteBufferSize: 512, + CustomRoundTripper: func(next http.RoundTripper) (http.RoundTripper, error) { return nil, errors.New("error") }, + }, + shouldError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + client, err := test.settings.ToClient() + if test.shouldError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + transport := client.Transport.(*http.Transport) + assert.EqualValues(t, 1024, transport.ReadBufferSize) + assert.EqualValues(t, 512, transport.WriteBufferSize) + }) + } +} + +func TestHTTPClientSettingsError(t *testing.T) { + tests := []struct { + settings HTTPClientSettings + err string + }{ + { + err: "^failed to load TLS config: failed to load CA CertPool: failed to load CA /doesnt/exist:", + settings: HTTPClientSettings{ + Endpoint: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/doesnt/exist", + }, + Insecure: false, + ServerName: "", + }, + }, + }, + { + err: "^failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither", + settings: HTTPClientSettings{ + Endpoint: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "/doesnt/exist", + }, + Insecure: false, + ServerName: "", + }, + }, + }, + } + for _, test := range tests { + t.Run(test.err, func(t *testing.T) { + _, err := test.settings.ToClient() + assert.Regexp(t, test.err, err) + }) + } +} + +func TestHTTPServerSettingsError(t *testing.T) { + tests := []struct { + settings HTTPServerSettings + err string + }{ + { + err: "^failed to load TLS config: failed to load CA CertPool: failed to load CA /doesnt/exist:", + settings: HTTPServerSettings{ + Endpoint: "", + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/doesnt/exist", + }, + }, + }, + }, + { + err: "^failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither", + settings: HTTPServerSettings{ + Endpoint: "", + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "/doesnt/exist", + }, + }, + }, + }, + { + err: "^failed to load TLS config: failed to load client CA CertPool: failed to load CA /doesnt/exist:", + settings: HTTPServerSettings{ + Endpoint: "", + TLSSetting: &configtls.TLSServerSetting{ + ClientCAFile: "/doesnt/exist", + }, + }, + }, + } + for _, test := range tests { + t.Run(test.err, func(t *testing.T) { + _, err := test.settings.ToListener() + assert.Regexp(t, test.err, err) + }) + } +} + +func TestHttpReception(t *testing.T) { + tests := []struct { + name string + tlsServerCreds *configtls.TLSServerSetting + tlsClientCreds *configtls.TLSClientSetting + hasError bool + }{ + { + name: "noTLS", + tlsServerCreds: nil, + tlsClientCreds: &configtls.TLSClientSetting{ + Insecure: true, + }, + }, + { + name: "TLS", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + ServerName: "localhost", + }, + }, + { + name: "NoServerCertificates", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + ServerName: "localhost", + }, + hasError: true, + }, + { + name: "mTLS", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + ClientCAFile: path.Join(".", "testdata", "ca.crt"), + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "client.crt"), + KeyFile: path.Join(".", "testdata", "client.key"), + }, + ServerName: "localhost", + }, + }, + { + name: "NoClientCertificate", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + ClientCAFile: path.Join(".", "testdata", "ca.crt"), + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + }, + ServerName: "localhost", + }, + hasError: true, + }, + { + name: "WrongClientCA", + tlsServerCreds: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + ClientCAFile: path.Join(".", "testdata", "server.crt"), + }, + tlsClientCreds: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: path.Join(".", "testdata", "ca.crt"), + CertFile: path.Join(".", "testdata", "client.crt"), + KeyFile: path.Join(".", "testdata", "client.key"), + }, + ServerName: "localhost", + }, + hasError: true, + }, + } + // prepare + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hss := &HTTPServerSettings{ + Endpoint: "localhost:0", + TLSSetting: tt.tlsServerCreds, + } + ln, err := hss.ToListener() + assert.NoError(t, err) + s := hss.ToServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, errWrite := fmt.Fprint(w, "test") + assert.NoError(t, errWrite) + })) + + go func() { + _ = s.Serve(ln) + }() + + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + prefix := "https://" + if tt.tlsClientCreds.Insecure { + prefix = "http://" + } + + hcs := &HTTPClientSettings{ + Endpoint: prefix + ln.Addr().String(), + TLSSetting: *tt.tlsClientCreds, + } + client, errClient := hcs.ToClient() + assert.NoError(t, errClient) + resp, errResp := client.Get(hcs.Endpoint) + if tt.hasError { + assert.Error(t, errResp) + } else { + assert.NoError(t, errResp) + body, errRead := ioutil.ReadAll(resp.Body) + assert.NoError(t, errRead) + assert.Equal(t, "test", string(body)) + } + require.NoError(t, s.Close()) + }) + } +} + +func TestHttpCors(t *testing.T) { + hss := &HTTPServerSettings{ + Endpoint: "localhost:0", + CorsOrigins: []string{"allowed-*.com"}, + } + + ln, err := hss.ToListener() + assert.NoError(t, err) + s := hss.ToServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + go func() { + _ = s.Serve(ln) + }() + + // TODO: make starting server deterministic + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + url := fmt.Sprintf("http://%s", ln.Addr().String()) + + // Verify allowed domain gets responses that allow CORS. + verifyCorsResp(t, url, "allowed-origin.com", 200, true) + + // Verify disallowed domain gets responses that disallow CORS. + verifyCorsResp(t, url, "disallowed-origin.com", 200, false) + + require.NoError(t, s.Close()) +} + +func verifyCorsResp(t *testing.T, url string, origin string, wantStatus int, wantAllowed bool) { + req, err := http.NewRequest("OPTIONS", url, nil) + require.NoError(t, err, "Error creating trace OPTIONS request: %v", err) + req.Header.Set("Origin", origin) + req.Header.Set("Access-Control-Request-Method", "POST") + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err, "Error sending OPTIONS to http server: %v", err) + + err = resp.Body.Close() + if err != nil { + t.Errorf("Error closing OPTIONS response body, %v", err) + } + + assert.Equal(t, wantStatus, resp.StatusCode) + + gotAllowOrigin := resp.Header.Get("Access-Control-Allow-Origin") + gotAllowMethods := resp.Header.Get("Access-Control-Allow-Methods") + + wantAllowOrigin := "" + wantAllowMethods := "" + if wantAllowed { + wantAllowOrigin = origin + wantAllowMethods = "POST" + } + assert.Equal(t, wantAllowOrigin, gotAllowOrigin) + assert.Equal(t, wantAllowMethods, gotAllowMethods) +} + +func ExampleHTTPServerSettings() { + settings := HTTPServerSettings{ + Endpoint: ":443", + } + s := settings.ToServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) + l, err := settings.ToListener() + if err != nil { + panic(err) + } + if err = s.Serve(l); err != nil { + panic(err) + } +} + +func TestHttpHeaders(t *testing.T) { + tests := []struct { + name string + headers map[string]string + }{ + { + "with_headers", + map[string]string{ + "header1": "value1", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for k, v := range tt.headers { + assert.Equal(t, r.Header.Get(k), v) + } + w.WriteHeader(200) + })) + defer server.Close() + serverURL, _ := url.Parse(server.URL) + setting := HTTPClientSettings{ + Endpoint: serverURL.String(), + TLSSetting: configtls.TLSClientSetting{}, + ReadBufferSize: 0, + WriteBufferSize: 0, + Timeout: 0, + Headers: map[string]string{ + "header1": "value1", + }, + } + client, _ := setting.ToClient() + req, err := http.NewRequest("GET", setting.Endpoint, nil) + assert.NoError(t, err) + client.Do(req) + }) + } +} diff --git a/internal/otel_collector/config/confighttp/testdata/ca.crt b/internal/otel_collector/config/confighttp/testdata/ca.crt new file mode 100644 index 00000000000..9ab794b7ffc --- /dev/null +++ b/internal/otel_collector/config/confighttp/testdata/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CCQDgJLdDKyhMRTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB +VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM +CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIwMDkyMjA1MjIx +MFoXDTMwMDkyMDA1MjIxMFowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry +YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV +BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AMKJc2wz8eLzAPonO37JahdY1Rt1dkRzQuung2Fe5O8UnbtEnDc7N7fbRLBgSRl0 +F5+V2USHCtYfAJ0tLifmInLOfEgmxIB2HNnwVLwDAnyXzp6NQEVw51bsILMTuFfB +mgp8Jq8KokGGOOh6GmM9h0a3KVdpxqPD+088t8AAwZrO5dHNIxZ4Bq471Stvcm7Z +jAWAoRsjceVdGr82+iB9wTio/FIeygb5rO5Ju1GMisR1LgJ6apDv9FrtWdorRxnb +qFMXdPvMyM34oIRT6bxETSIYYHjozUz1/H0GB4NeGUbov0etnviTl+oMpRj0vZpT +DB8SD1XjHGOpbUZ6ibgUrWMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEApBKbrk4g +Bd9/T1P3T3BBWOn8iMYLapBeapP6kVW9VtrpSvwKv6dchhh2Iizz5PnEEKBnU7ho ++GQXrKM0L/ejQeDqEo0lkZLJmovGNzXTNBGhgcVJ37qt0Bt58SoCA2Oc8dn4gtyR +eGi2lSVDUc+kiZWm9lUfTcwQeqTb8oS64DwJR8f11uX3NJn9N7fbwino60D5U7Na +ojO9ua4W3K5C8cuNEjssyE6qjSQ4lhXBlHxA9viSdQSQN0Lv/AH1s175jQ7G24jM +58v5DC7P0oodiOdr9Z0hndK8c1mgB2fTTme+h9iDYVttbMHoARYCWSy02/ZzHRah +tAEubJUHnzv5vA== +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/confighttp/testdata/client.crt b/internal/otel_collector/config/confighttp/testdata/client.crt new file mode 100644 index 00000000000..ee5dab3c561 --- /dev/null +++ b/internal/otel_collector/config/confighttp/testdata/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdbMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjEwWhcNMzAwOTIwMDUyMjEwWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA0TJ19WUFfGqdkus7kOqFQkR570sncfM0oLWK3Jzrqf0LO18BKv3LKQ/N +vVVkKa4IXkGo+oopzb/I5GQuCkp0LFz+lIfPioiEF1DQ+MJAHBEvNerwSgezlYbh +2cg/NS+f7CTe98AaEIiA+UtXDCWq2mttBLSckkvDzFpB++WL5HjonUyzE03ijAli +CJvMZmVFY7Q/uP00S0tyvgskHkeQXxVQ7rBlg43OYKRs0lXyEOYypv+2i7vxb2NM +rZciZa9wNxdWHPukeMsY2HEpZPEAochE3zppfomjc2T+B2F3uBkB0YcK0K4ugO2+ +KuzpIhoQpmdFwXjmnLjaYUZ7s+XUrwIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQB6 +II9PnbwfjQPn5vx6vEnoe6HQV0V+xnh5zfD8DJ6hM42HbGSBAqD64z6odkx7jKdj +B0tdxgx9eN0/tp15ss3h5BRksVMf1k4fFG0MY/jS5GDX4V8G3e/4SbrkNjXdA2UR +i0QMB2nyPObkpCVIRDwtdv0E416Rpm1GDtcjjuyBRAfODkj/LZ3nmwzEtXwo2XG3 +hthyC/4x6LmK0g4siA0ru8vtwUHh7d7A7rcZDPGajA+B9ByBQT3GzCND8NVqbyiq +G/XpRVQ4XmE2Vdg05hDVpHzgmyii6eIrDnQd4XrHBWLV6JuUMGu1goQDTxKlyt0p +gPm/gT00VmSUUh4QLX91 +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/confighttp/testdata/client.key b/internal/otel_collector/config/confighttp/testdata/client.key new file mode 100644 index 00000000000..e5876e89835 --- /dev/null +++ b/internal/otel_collector/config/confighttp/testdata/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0TJ19WUFfGqdkus7kOqFQkR570sncfM0oLWK3Jzrqf0LO18B +Kv3LKQ/NvVVkKa4IXkGo+oopzb/I5GQuCkp0LFz+lIfPioiEF1DQ+MJAHBEvNerw +SgezlYbh2cg/NS+f7CTe98AaEIiA+UtXDCWq2mttBLSckkvDzFpB++WL5HjonUyz +E03ijAliCJvMZmVFY7Q/uP00S0tyvgskHkeQXxVQ7rBlg43OYKRs0lXyEOYypv+2 +i7vxb2NMrZciZa9wNxdWHPukeMsY2HEpZPEAochE3zppfomjc2T+B2F3uBkB0YcK +0K4ugO2+KuzpIhoQpmdFwXjmnLjaYUZ7s+XUrwIDAQABAoIBAEH+CxwIbDydXWv1 +bOsAMF2BQH3uVVkrAZUY7988WVNckeh+xd2MBkTDyYFKqLhFQDqLuAShBSL0tyjl +OWjhp9g+1ciBN0VaX2EDi4iNrq+r9BqsLHUODObEkAalltruVSKnVvcM0Kwag6Ug +0Srxzv3sGY38c8/quq+CYYJXHVRLBX8vQ70rwp+BKEipce18/kVxywaYGYqjzwcA +iiG6QJlyl7BrOUK8KPpyvC0OhnDpDdCO875MuBLzvulFcqvGbGNHcgRqAEk1xH5b +SQoiZBaZGWK1Ns7+bwCtcVBhvxsWqIffIvXAG74DQqpIdgmR1hVLx1e4HxVBHpDQ +Z096yVECgYEA+9M2xKbzyUJCc4MfOjRG0V1lzECrBt0sv6GMY2o8PFj1+MRONmHV +G556oxeK1NT9r6KRxK8NKSxkR775HDgSFd3VdFLpmCDQu/z/PSWoSo+0jmToOX9t +eykF4MCLhU8ck2AiDne4MB7MNKqPesbGsmK2IwPkHLGQ8Sz0367AqFMCgYEA1KpT +tafR5D/yq4iC51o6PjQ4gMn7vpiGvkU9VVEzZQRGaP5W3ssTEh9b58wlMTOxQE3Z +cpoVNRXAg1jOkKa0NZm5SOOz1PpdNINIbGpVVrx/cUkhKHDEj+uDt72fS8cyU14n +52jlh+3LpG1UyLNX7eod/xv+Wo5oLe3fvJAzprUCgYEA5PtBqb9FnZOqaO6pznsK +igWrMvb6jNtAfV+gECXhb95Ui0e09q4u4VZRnUsi6jRiGPpyIa4rAW1kIfj8+zPg +/hEgrw1VawcrxkResnMze9kADRqkLuQ34O2EcsGiHC27hia70Pv7d4YJmToeDT4C +HuKzS1OWcKDlcue2Ik780BECgYAVwsACDIQLqQd5yeQrLC5dgxZtBz39SLow6gDW +pBJwObnCsJPPBFSVPCQ5WchMeo+eltizQ1T8M5eZWRL59jTmby5oaPRTzLKQ1wYo +IdFNqMgZnXQJIVDbsSuvN3X/WQirQy0uHqut9wUpdA6C4ucSbyxWmFS0i3HZkUed +kdvXKQKBgGpQ7jpDzNh3zXO7AU4m4FlAN+N+dVXCP0C8DwcyRM69Sioyybh5lVww +QfQoSs3/m6/XZ5r6l9OvM6oUGfovk9Ja7NN8aqXQ4lwrET4SblRroKXQqzvp0aJ2 +XwHVfW5N9DcAQtQQzyWHxcKLyNOZLYQmBRsKuw4wu2I/rn8gWsy5 +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/config/confighttp/testdata/server.crt b/internal/otel_collector/config/confighttp/testdata/server.crt new file mode 100644 index 00000000000..b1346ea87b4 --- /dev/null +++ b/internal/otel_collector/config/confighttp/testdata/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdaMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjEwWhcNMzAwOTIwMDUyMjEwWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAzIHy8CASiF6aI3CsI6RGlJBZExPk/Utvlp89ga42g+e1YxZUZtMm79A4 +uVOXnNsRvFtgiRA8xHrdcNcgCDBhBA7p5vQC/KgJymM6cdiNTStQbhvl7qpgyU8d +PYQNqKaaHo5ceW/AQM2z5XZRnak2HhI7VhO4QLOfp7CB0XvpFGG2lWpZ/xEHGIit +PcUQUmiROPremupF7mB04HQVH3TxWTtmHwvfWICbjO6gMfIT3me/4HrECA/WX2hj +ffP1HPfPz3ZU8UMWmodQif2/aX7auh1CfqpJbVVYMCtMr7WCmKXiYkrMK6osaoku +eCgM+ouNf1rXnzxdX6ApwZXrx9t/3QIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBi +K8J0Vy3/CHbYcQTg2UK0OCo08auuUUy3j9nSM2KfeeIKdM0DlRgLyIH63VwZNWFI +0tk27MONgsnXPFuEfUg4QG0QXx+rny10ckzI74ff1tOvu3LFkKRYafz3a1LfWW2Q +WDnta3lC+OsSKMEze1Bd4mvHZoiqTvkLbpmAudoWF7n+VSNXoOjizoMissqxy8iD +uZ6ChBWvJ1V+MtttXP0D7rSJuB0bkVwtcyEMNkUh7GrZVl61EcMQfjg5Vwsntdrv +cIIubS1F8uzT7ABLOGhiYm6gr3HPHsQp/t8sXNWIbjTmoueYBK215rvY3FQrzvAW +hNltkVRow5h+FyA/WVDN +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/confighttp/testdata/server.key b/internal/otel_collector/config/confighttp/testdata/server.key new file mode 100644 index 00000000000..cef03655b40 --- /dev/null +++ b/internal/otel_collector/config/confighttp/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAzIHy8CASiF6aI3CsI6RGlJBZExPk/Utvlp89ga42g+e1YxZU +ZtMm79A4uVOXnNsRvFtgiRA8xHrdcNcgCDBhBA7p5vQC/KgJymM6cdiNTStQbhvl +7qpgyU8dPYQNqKaaHo5ceW/AQM2z5XZRnak2HhI7VhO4QLOfp7CB0XvpFGG2lWpZ +/xEHGIitPcUQUmiROPremupF7mB04HQVH3TxWTtmHwvfWICbjO6gMfIT3me/4HrE +CA/WX2hjffP1HPfPz3ZU8UMWmodQif2/aX7auh1CfqpJbVVYMCtMr7WCmKXiYkrM +K6osaokueCgM+ouNf1rXnzxdX6ApwZXrx9t/3QIDAQABAoIBABRe3VQN3cq3oaLm +Fj92nZEuz7CWyrhwSy01r2q7b7Kz4d182+tiHP7GPuA282MsbxfUAkmk1Gi91FDp +HMe0CfXdhm7631FLa649NBUi/PAy4FAXd0/OqNVkjAUUokeqUK+6fnuaJgxOcRzq +LDcII9va9Q4d6LyJJ94MNuIm9ZCR/Yg3S3X6TnW+fh6CWw0NL0MV9/7JLPLUZglT +UsFayjNUUxXqrL1OuQ6yyEEVxPtu0rBD9n6s3LGf7iWrmltRaPOkq6feaU741PMV +uF7YUB5oNOVSJNWDFg9cxxJfpO+5I05YA0oiahrrd1jLu+j/1LdKvDSXBy2bLnIu +m3VbigECgYEA5qZdBj/id/HMCoX/Pw/jXzv0OEkOIULc1Jh1il6GUavCYjwPI0KE +tzJUjYfEa7ToymZtcrRYg4qoG7diWHndW4J7hmxj17b+PlwCsoU/TMkxLgw9mmc0 +Qp6fn8VOdGZ4ysTGn80Pn9zRDApy5f29b070cIHjFBXZnREuE0o8hOsCgYEA4vwK +C7JoHFNnxzpDj2aW6JDmNxMRpNOeQkC5rRR6uDjTHdaGq3WI0aGqvc6l47kIcb9w +MJiapHWCzJNc56jqmb/lgDku4sGRs5g6meOYENCYf9aKZzG9fkG/gGZf3eg2Yp2z +KwfKsk4g1HUdwIcC6dTQTgsGoPMYReP44R6Z/FcCgYBeb4Us9uExvPWO5XgxiL7O +kkyW8wpvAeJKxTVy9urF665F7FNCW4zdOSU3YXxBoSujGzb6vO50xUO5PWdt1E+W +lSEgU6a5frowLBoKn9XgCYwyT161pkXWdP3kO7O4ovAYDWNJsHsSOCX7aRfMJQz3 +0vrwSa4A3kVgMtWLnlyTCwKBgQDKfpLvsG9Upcu1XnMbISiLvYjDpU1eQDO1Y0zB +7b01T+x3eASYPbibW6CYyBwSNeYko+aQU/PRt8vCecyuFnGETD+PznPXc1xqXeoZ +k4L7rTv/AARk32jvk/Qlti7cJuctvwYx4zefLjf3kavDMC8XL/XNSeTV/UiwQRqs +qsIw7QKBgDSHMszYPoSaihjPFwIlEDqjk6QNUm4MuISV9/rdRuA+RzBVUOhtWnI0 +Oxh71iELCWxxKn73G0DqIMUfMLvnR7IMBFPS7wn0T13eF9f7LanJYCEXPAJU5OsZ +RNgLchEoy8xRALwfPxYncMEEzOcvexZWXs+76vZc30BXfvEWuXbS +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/config/configmodels/configmodels.go b/internal/otel_collector/config/configmodels/configmodels.go new file mode 100644 index 00000000000..3084eab5b16 --- /dev/null +++ b/internal/otel_collector/config/configmodels/configmodels.go @@ -0,0 +1,223 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package configmodels defines the data models for entities. This file defines the +// models for configuration format. The defined entities are: +// Config (the top-level structure), Receivers, Exporters, Processors, Pipelines. +package configmodels + +/* +Receivers, Exporters and Processors typically have common configuration settings, however +sometimes specific implementations will have extra configuration settings. +This requires the configuration data for these entities to be polymorphic. + +To satisfy these requirements we declare interfaces Receiver, Exporter, Processor, +which define the behavior. We also provide helper structs ReceiverSettings, ExporterSettings, +ProcessorSettings, which define the common settings and un-marshaling from config files. + +Specific Receivers/Exporters/Processors are expected to at the minimum implement the +corresponding interface and if they have additional settings they must also extend +the corresponding common settings struct (the easiest approach is to embed the common struct). +*/ + +// Config defines the configuration for the various elements of collector or agent. +type Config struct { + Receivers + Exporters + Processors + Extensions + Service +} + +// Type is the component type as it is used in the config. +type Type string + +// NamedEntity is a configuration entity that has a type and a name. +type NamedEntity interface { + Type() Type + Name() string + SetName(name string) +} + +// Receiver is the configuration of a receiver. Specific receivers must implement this +// interface and will typically embed ReceiverSettings struct or a struct that extends it. +type Receiver interface { + NamedEntity +} + +// Receivers is a map of names to Receivers. +type Receivers map[string]Receiver + +// Exporter is the configuration of an exporter. +type Exporter interface { + NamedEntity +} + +// Exporters is a map of names to Exporters. +type Exporters map[string]Exporter + +// Processor is the configuration of a processor. Specific processors must implement this +// interface and will typically embed ProcessorSettings struct or a struct that extends it. +type Processor interface { + NamedEntity +} + +// Processors is a map of names to Processors. +type Processors map[string]Processor + +// DataType is the data type that is supported for collection. We currently support +// collecting metrics, traces and logs, this can expand in the future. + +type DataType string + +// Currently supported data types. Add new data types here when new types are supported in the future. +const ( + // TracesDataType is the data type tag for traces. + TracesDataType DataType = "traces" + + // MetricsDataType is the data type tag for metrics. + MetricsDataType DataType = "metrics" + + // LogsDataType is the data type tag for logs. + LogsDataType DataType = "logs" +) + +// Pipeline defines a single pipeline. +type Pipeline struct { + Name string `mapstructure:"-"` + InputType DataType `mapstructure:"-"` + Receivers []string `mapstructure:"receivers"` + Processors []string `mapstructure:"processors"` + Exporters []string `mapstructure:"exporters"` +} + +// Pipelines is a map of names to Pipelines. +type Pipelines map[string]*Pipeline + +// Extension is the configuration of a service extension. Specific extensions +// must implement this interface and will typically embed ExtensionSettings +// struct or a struct that extends it. +type Extension interface { + NamedEntity +} + +// Extensions is a map of names to extensions. +type Extensions map[string]Extension + +// Service defines the configurable components of the service. +type Service struct { + // Extensions is the ordered list of extensions configured for the service. + Extensions []string `mapstructure:"extensions"` + + // Pipelines is the set of data pipelines configured for the service. + Pipelines Pipelines `mapstructure:"pipelines"` +} + +// Below are common setting structs for Receivers, Exporters and Processors. +// These are helper structs which you can embed when implementing your specific +// receiver/exporter/processor config storage. + +// ReceiverSettings defines common settings for a receiver configuration. +// Specific receivers can embed this struct and extend it with more fields if needed. +type ReceiverSettings struct { + TypeVal Type `mapstructure:"-"` + NameVal string `mapstructure:"-"` +} + +// Name gets the receiver name. +func (rs *ReceiverSettings) Name() string { + return rs.NameVal +} + +// SetName sets the receiver name. +func (rs *ReceiverSettings) SetName(name string) { + rs.NameVal = name +} + +// Type sets the receiver type. +func (rs *ReceiverSettings) Type() Type { + return rs.TypeVal +} + +// ExporterSettings defines common settings for an exporter configuration. +// Specific exporters can embed this struct and extend it with more fields if needed. +type ExporterSettings struct { + TypeVal Type `mapstructure:"-"` + NameVal string `mapstructure:"-"` +} + +var _ Exporter = (*ExporterSettings)(nil) + +// Name gets the exporter name. +func (es *ExporterSettings) Name() string { + return es.NameVal +} + +// SetName sets the exporter name. +func (es *ExporterSettings) SetName(name string) { + es.NameVal = name +} + +// Type sets the exporter type. +func (es *ExporterSettings) Type() Type { + return es.TypeVal +} + +// ProcessorSettings defines common settings for a processor configuration. +// Specific processors can embed this struct and extend it with more fields if needed. +type ProcessorSettings struct { + TypeVal Type `mapstructure:"-"` + NameVal string `mapstructure:"-"` +} + +// Name gets the processor name. +func (proc *ProcessorSettings) Name() string { + return proc.NameVal +} + +// SetName sets the processor name. +func (proc *ProcessorSettings) SetName(name string) { + proc.NameVal = name +} + +// Type sets the processor type. +func (proc *ProcessorSettings) Type() Type { + return proc.TypeVal +} + +var _ Processor = (*ProcessorSettings)(nil) + +// ExtensionSettings defines common settings for a service extension configuration. +// Specific extensions can embed this struct and extend it with more fields if needed. +type ExtensionSettings struct { + TypeVal Type `mapstructure:"-"` + NameVal string `mapstructure:"-"` +} + +// Name gets the extension name. +func (ext *ExtensionSettings) Name() string { + return ext.NameVal +} + +// SetName sets the extension name. +func (ext *ExtensionSettings) SetName(name string) { + ext.NameVal = name +} + +// Type sets the extension type. +func (ext *ExtensionSettings) Type() Type { + return ext.TypeVal +} + +var _ Extension = (*ExtensionSettings)(nil) diff --git a/internal/otel_collector/config/confignet/README.md b/internal/otel_collector/config/confignet/README.md new file mode 100644 index 00000000000..2a4053da59f --- /dev/null +++ b/internal/otel_collector/config/confignet/README.md @@ -0,0 +1,18 @@ +# Network Configuration Settings + +[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/master/receiver/README.md) +leverage network configuration to set connection and transport information. + +- `endpoint`: Configures the address for this network connection. For TCP and + UDP networks, the address has the form "host:port". The host must be a + literal IP address, or a host name that can be resolved to IP addresses. The + port must be a literal port number or a service name. If the host is a + literal IPv6 address it must be enclosed in square brackets, as in + "[2001:db8::1]:80" or "[fe80::1%zone]:80". The zone specifies the scope of + the literal IPv6 address as defined in RFC 4007. +- `transport`: Known protocols are "tcp", "tcp4" (IPv4-only), "tcp6" + (IPv6-only), "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" + (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket". + +Note that for TCP receivers only the `endpoint` configuration setting is +required. diff --git a/internal/otel_collector/config/confignet/confignet.go b/internal/otel_collector/config/confignet/confignet.go new file mode 100644 index 00000000000..ec55a5a01e5 --- /dev/null +++ b/internal/otel_collector/config/confignet/confignet.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package confignet + +import ( + "net" +) + +// NetAddr represents a network endpoint address. +type NetAddr struct { + // Endpoint configures the address for this network connection. + // For TCP and UDP networks, the address has the form "host:port". The host must be a literal IP address, + // or a host name that can be resolved to IP addresses. The port must be a literal port number or a service name. + // If the host is a literal IPv6 address it must be enclosed in square brackets, as in "[2001:db8::1]:80" or + // "[fe80::1%zone]:80". The zone specifies the scope of the literal IPv6 address as defined in RFC 4007. + Endpoint string `mapstructure:"endpoint"` + + // Transport to use. Known protocols are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only), + // "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket". + Transport string `mapstructure:"transport"` +} + +func (na *NetAddr) Dial() (net.Conn, error) { + return net.Dial(na.Transport, na.Endpoint) +} + +func (na *NetAddr) Listen() (net.Listener, error) { + return net.Listen(na.Transport, na.Endpoint) +} + +// TCPAddr represents a tcp endpoint address. +type TCPAddr struct { + // Endpoint configures the address for this network connection. + // The address has the form "host:port". The host must be a literal IP address, or a host name that can be + // resolved to IP addresses. The port must be a literal port number or a service name. + // If the host is a literal IPv6 address it must be enclosed in square brackets, as in "[2001:db8::1]:80" or + // "[fe80::1%zone]:80". The zone specifies the scope of the literal IPv6 address as defined in RFC 4007. + Endpoint string `mapstructure:"endpoint"` +} + +func (na *TCPAddr) Dial() (net.Conn, error) { + return net.Dial("tcp", na.Endpoint) +} + +func (na *TCPAddr) Listen() (net.Listener, error) { + return net.Listen("tcp", na.Endpoint) +} diff --git a/internal/otel_collector/config/confignet/confignet_test.go b/internal/otel_collector/config/confignet/confignet_test.go new file mode 100644 index 00000000000..885801fdd27 --- /dev/null +++ b/internal/otel_collector/config/confignet/confignet_test.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package confignet + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNetAddr(t *testing.T) { + nas := &NetAddr{ + Endpoint: "localhost:0", + Transport: "tcp", + } + ln, err := nas.Listen() + assert.NoError(t, err) + done := make(chan bool, 1) + + go func() { + conn, errGo := ln.Accept() + assert.NoError(t, errGo) + buf := make([]byte, 10) + var numChr int + numChr, errGo = conn.Read(buf) + assert.NoError(t, errGo) + assert.Equal(t, "test", string(buf[:numChr])) + assert.NoError(t, conn.Close()) + done <- true + }() + + nac := &NetAddr{ + Endpoint: ln.Addr().String(), + Transport: "tcp", + } + var conn net.Conn + conn, err = nac.Dial() + assert.NoError(t, err) + _, err = conn.Write([]byte("test")) + assert.NoError(t, err) + assert.NoError(t, conn.Close()) + <-done + assert.NoError(t, ln.Close()) +} + +func TestTcpAddr(t *testing.T) { + nas := &TCPAddr{ + Endpoint: "localhost:0", + } + ln, err := nas.Listen() + assert.NoError(t, err) + done := make(chan bool, 1) + + go func() { + conn, errGo := ln.Accept() + assert.NoError(t, errGo) + buf := make([]byte, 10) + var numChr int + numChr, errGo = conn.Read(buf) + assert.NoError(t, errGo) + assert.Equal(t, "test", string(buf[:numChr])) + assert.NoError(t, conn.Close()) + done <- true + }() + + nac := &TCPAddr{ + Endpoint: ln.Addr().String(), + } + var conn net.Conn + conn, err = nac.Dial() + assert.NoError(t, err) + _, err = conn.Write([]byte("test")) + assert.NoError(t, err) + assert.NoError(t, conn.Close()) + <-done + assert.NoError(t, ln.Close()) +} diff --git a/internal/otel_collector/config/configtelemetry/configtelemetry.go b/internal/otel_collector/config/configtelemetry/configtelemetry.go new file mode 100644 index 00000000000..0c01c70dbd0 --- /dev/null +++ b/internal/otel_collector/config/configtelemetry/configtelemetry.go @@ -0,0 +1,127 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configtelemetry + +import ( + "flag" + "fmt" + "strings" +) + +const ( + // LevelNone indicates that no telemetry data should be collected. + LevelNone Level = iota - 1 + // LevelBasic is the recommended and covers the basics of the service telemetry. + LevelBasic + // LevelNormal adds some other indicators on top of basic. + LevelNormal + // LevelDetailed adds dimensions and views to the previous levels. + LevelDetailed + + levelNoneStr = "none" + levelBasicStr = "basic" + levelNormalStr = "normal" + levelDetailedStr = "detailed" + + metricsLevelCfg = "metrics-level" +) + +var metricsLevelPtr = new(Level) + +// Flags is a helper func, to add the telemetry config flags to the service that exposes +// the application flags. +func Flags(flags *flag.FlagSet) { + flags.Var( + metricsLevelPtr, + metricsLevelCfg, + "Output level of telemetry metrics (none, basic, normal, detailed)") +} + +// Level is the level of internal telemetry (metrics, logs, traces about the component itself) +// that every component should generate. +type Level int8 + +var _ flag.Value = (*Level)(nil) + +func (l *Level) String() string { + switch *l { + case LevelNone: + return levelNoneStr + case LevelBasic: + return levelBasicStr + case LevelNormal: + return levelNormalStr + case LevelDetailed: + return levelDetailedStr + } + return "unknown" +} + +func (l *Level) Set(s string) error { + lvl, err := parseLevel(s) + if err != nil { + return err + } + *l = lvl + return nil +} + +// GetMetricsLevelFlagValue returns the value of the "--metrics-level" flag. +// IMPORTANT: This must be used only in the core collector code for the moment. +func GetMetricsLevelFlagValue() Level { + return *metricsLevelPtr +} + +// TelemetrySetting exposes the common Telemetry configuration for one component. +type TelemetrySetting struct { + // MetricsLevelStr is the level of telemetry metrics, the possible values are: + // - "none" indicates that no telemetry data should be collected; + // - "basic" is the recommended and covers the basics of the service telemetry. + // - "normal" adds some other indicators on top of basic. + // - "detailed" adds dimensions and views to the previous levels. + MetricsLevelStr string `mapstructure:"metrics_level"` +} + +// DefaultTelemetrySetting returns the default TelemetrySetting. +// The level is set to the "--metrics-level" flag if set, otherwise the default "basic" level. +func DefaultTelemetrySetting() TelemetrySetting { + return TelemetrySetting{ + MetricsLevelStr: metricsLevelPtr.String(), + } +} + +// GetMetricsLevel returns the parsed level, or error if unknown value. +// Empty string is consider unknown value. +func (ts TelemetrySetting) GetMetricsLevel() (Level, error) { + return parseLevel(ts.MetricsLevelStr) +} + +// ParseLevel returns the Level represented by the string. The parsing is case-insensitive +// and it returns error if the string value is unknown. +func parseLevel(str string) (Level, error) { + str = strings.ToLower(str) + + switch str { + case levelNoneStr: + return LevelNone, nil + case levelBasicStr: + return LevelBasic, nil + case levelNormalStr: + return LevelNormal, nil + case levelDetailedStr: + return LevelDetailed, nil + } + return LevelNone, fmt.Errorf("unknown metrics level %q", str) +} diff --git a/internal/otel_collector/config/configtelemetry/configtelemetry_test.go b/internal/otel_collector/config/configtelemetry/configtelemetry_test.go new file mode 100644 index 00000000000..4512bb5750b --- /dev/null +++ b/internal/otel_collector/config/configtelemetry/configtelemetry_test.go @@ -0,0 +1,167 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configtelemetry + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseFrom(t *testing.T) { + tests := []struct { + str string + level Level + err bool + }{ + { + str: "", + level: LevelNone, + err: true, + }, + { + str: "other_string", + level: LevelNone, + err: true, + }, + { + str: levelNoneStr, + level: LevelNone, + }, + { + str: levelBasicStr, + level: LevelBasic, + }, + { + str: levelNormalStr, + level: LevelNormal, + }, + { + str: levelDetailedStr, + level: LevelDetailed, + }, + } + + for _, test := range tests { + t.Run(test.str, func(t *testing.T) { + lvl, err := parseLevel(test.str) + if test.err { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, test.level, lvl) + }) + } +} + +func TestLevelSet(t *testing.T) { + tests := []struct { + str string + level Level + err bool + }{ + { + str: "", + level: LevelNone, + err: true, + }, + { + str: "other_string", + level: LevelNone, + err: true, + }, + { + str: levelNoneStr, + level: LevelNone, + }, + { + str: levelBasicStr, + level: LevelBasic, + }, + { + str: levelNormalStr, + level: LevelNormal, + }, + { + str: levelDetailedStr, + level: LevelDetailed, + }, + } + for _, test := range tests { + t.Run(test.str, func(t *testing.T) { + lvl := new(Level) + err := lvl.Set(test.str) + if test.err { + assert.Error(t, err) + assert.Equal(t, LevelBasic, *lvl) + } else { + assert.NoError(t, err) + assert.Equal(t, test.level, *lvl) + } + }) + } +} + +func TestLevelString(t *testing.T) { + tests := []struct { + str string + level Level + err bool + }{ + { + str: "unknown", + level: Level(-10), + }, + { + str: levelNoneStr, + level: LevelNone, + }, + { + str: levelBasicStr, + level: LevelBasic, + }, + { + str: levelNormalStr, + level: LevelNormal, + }, + { + str: levelDetailedStr, + level: LevelDetailed, + }, + } + for _, test := range tests { + t.Run(test.str, func(t *testing.T) { + assert.Equal(t, test.str, test.level.String()) + }) + } +} + +func TestTelemetrySettings(t *testing.T) { + ts := &TelemetrySetting{ + MetricsLevelStr: "unknown", + } + _, err := ts.GetMetricsLevel() + assert.Error(t, err) +} + +func TestDefaultTelemetrySettings(t *testing.T) { + ts := DefaultTelemetrySetting() + assert.Equal(t, levelBasicStr, ts.MetricsLevelStr) + lvl, err := ts.GetMetricsLevel() + require.NoError(t, err) + assert.Equal(t, LevelBasic, lvl) +} diff --git a/internal/otel_collector/config/configtest/configtest.go b/internal/otel_collector/config/configtest/configtest.go new file mode 100644 index 00000000000..3d75e3e5d35 --- /dev/null +++ b/internal/otel_collector/config/configtest/configtest.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configtest + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configmodels" +) + +// NewViperFromYamlFile creates a viper instance that reads the given fileName as yaml config +// and can then be used to unmarshal the file contents to objects. +// Example usage for testing can be found in configtest_test.go +func NewViperFromYamlFile(t *testing.T, fileName string) *viper.Viper { + // Read yaml config from file + v := config.NewViper() + v.SetConfigFile(fileName) + require.NoErrorf(t, v.ReadInConfig(), "unable to read the file %v", fileName) + + return v +} + +// LoadConfigFile loads a config from file. +func LoadConfigFile(t *testing.T, fileName string, factories component.Factories) (*configmodels.Config, error) { + v := NewViperFromYamlFile(t, fileName) + + // Load the config from viper using the given factories. + cfg, err := config.Load(v, factories) + if err != nil { + return nil, err + } + return cfg, config.ValidateConfig(cfg, zap.NewNop()) +} diff --git a/internal/otel_collector/config/configtest/configtest_test.go b/internal/otel_collector/config/configtest/configtest_test.go new file mode 100644 index 00000000000..e5680f572bc --- /dev/null +++ b/internal/otel_collector/config/configtest/configtest_test.go @@ -0,0 +1,127 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configtest + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" +) + +func TestLoadConfigFile(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + cfg, err := LoadConfigFile(t, "testdata/config.yaml", factories) + require.NoError(t, err, "Unable to load config") + + // Verify extensions. + assert.Equal(t, 3, len(cfg.Extensions)) + assert.Equal(t, "some string", cfg.Extensions["exampleextension/1"].(*componenttest.ExampleExtensionCfg).ExtraSetting) + + // Verify service. + assert.Equal(t, 2, len(cfg.Service.Extensions)) + assert.Equal(t, "exampleextension/0", cfg.Service.Extensions[0]) + assert.Equal(t, "exampleextension/1", cfg.Service.Extensions[1]) + + // Verify receivers + assert.Equal(t, 2, len(cfg.Receivers), "Incorrect receivers count") + + assert.Equal(t, + &componenttest.ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "examplereceiver", + NameVal: "examplereceiver", + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:1000", + }, + ExtraSetting: "some string", + }, + cfg.Receivers["examplereceiver"], + "Did not load receiver config correctly") + + assert.Equal(t, + &componenttest.ExampleReceiver{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: "examplereceiver", + NameVal: "examplereceiver/myreceiver", + }, + TCPAddr: confignet.TCPAddr{ + Endpoint: "localhost:12345", + }, + ExtraSetting: "some string", + }, + cfg.Receivers["examplereceiver/myreceiver"], + "Did not load receiver config correctly") + + // Verify exporters + assert.Equal(t, 2, len(cfg.Exporters), "Incorrect exporters count") + + assert.Equal(t, + &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter", + TypeVal: "exampleexporter", + }, + ExtraSetting: "some export string", + }, + cfg.Exporters["exampleexporter"], + "Did not load exporter config correctly") + + assert.Equal(t, + &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter/myexporter", + TypeVal: "exampleexporter", + }, + ExtraSetting: "some export string 2", + }, + cfg.Exporters["exampleexporter/myexporter"], + "Did not load exporter config correctly") + + // Verify Processors + assert.Equal(t, 1, len(cfg.Processors), "Incorrect processors count") + + assert.Equal(t, + &componenttest.ExampleProcessorCfg{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "exampleprocessor", + NameVal: "exampleprocessor", + }, + ExtraSetting: "some export string", + }, + cfg.Processors["exampleprocessor"], + "Did not load processor config correctly") + + // Verify Pipelines + assert.Equal(t, 1, len(cfg.Service.Pipelines), "Incorrect pipelines count") + + assert.Equal(t, + &configmodels.Pipeline{ + Name: "traces", + InputType: configmodels.TracesDataType, + Receivers: []string{"examplereceiver"}, + Processors: []string{"exampleprocessor"}, + Exporters: []string{"exampleexporter"}, + }, + cfg.Service.Pipelines["traces"], + "Did not load pipeline config correctly") +} diff --git a/internal/otel_collector/config/configtest/testdata/config.yaml b/internal/otel_collector/config/configtest/testdata/config.yaml new file mode 100644 index 00000000000..da82f64eb33 --- /dev/null +++ b/internal/otel_collector/config/configtest/testdata/config.yaml @@ -0,0 +1,28 @@ +receivers: + examplereceiver: + examplereceiver/myreceiver: + endpoint: "localhost:12345" + extra: "some string" + +processors: + exampleprocessor: + +exporters: + exampleexporter/myexporter: + extra: "some export string 2" + exampleexporter: + +extensions: + exampleextension/0: + exampleextension/disabled: + extra: "not present in the service" + exampleextension/1: + extra: "some string" + +service: + extensions: [exampleextension/0, exampleextension/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/configtls/README.md b/internal/otel_collector/config/configtls/README.md new file mode 100644 index 00000000000..7eda60cd074 --- /dev/null +++ b/internal/otel_collector/config/configtls/README.md @@ -0,0 +1,111 @@ +# TLS Configuration Settings + +Crypto TLS exposes a [variety of settings](https://godoc.org/crypto/tls). +Several of these settings are available for configuration within individual +receivers or exporters. + +Note that mutual TLS (mTLS) is also supported. + +## TLS / mTLS Configuration + +By default, TLS is enabled: + +- `insecure` (default = false): whether to enable client transport security for + the exporter's gRPC connection. See + [grpc.WithInsecure()](https://godoc.org/google.golang.org/grpc#WithInsecure). + +As a result, the following parameters are also required: + +- `cert_file`: Path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to false. +- `key_file`: Path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to false. + +A certificate authority may also need to be defined: + +- `ca_file`: Path to the CA cert. For a client this verifies the server + certificate. For a server this verifies client certificates. If empty uses + system root CA. Should only be used if `insecure` is set to false. + +Additionally you can configure TLS to be enabled but skip verifying the server's +certificate chain. This cannot be combined with `insecure` since `insecure` +won't use TLS at all. + +- `insecure_skip_verify` (default = false): whether to skip verifying the + certificate or not. + +How TLS/mTLS is configured depends on whether configuring the client or server. +See below for examples. + +## Client Configuration + +[Exporters](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/README.md) +leverage client configuration. + +Note that client configuration supports TLS configuration, however +configuration parameters are not defined under `tls_settings` like server +configuration. For more information, see [configtls +README](../configtls/README.md). + +Beyond TLS configuration, the following setting can optionally be configured: + +- `server_name_override`: If set to a non-empty string, it will override the + virtual host name of authority (e.g. :authority header field) in requests + (typically used for testing). + +Example: + +```yaml +exporters: + otlp: + endpoint: myserver.local:55690 + insecure: false + ca_file: server.crt + cert_file: client.crt + key_file: client.key + otlp/insecure: + endpoint: myserver.local:55690 + insecure: true + otlp/secure_no_verify: + endpoint: myserver.local:55690 + insecure: false + insecure_skip_verify: true +``` + +## Server Configuration + +[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/master/receiver/README.md) +leverage server configuration. + +Beyond TLS configuration, the following setting can optionally be configured +(required for mTLS): + +- `client_ca_file`: Path to the TLS cert to use by the server to verify a + client certificate. (optional) This sets the ClientCAs and ClientAuth to + RequireAndVerifyClientCert in the TLSConfig. Please refer to + https://godoc.org/crypto/tls#Config for more information. + +Example: + +```yaml +receivers: + otlp: + protocols: + grpc: + endpoint: mysite.local:55690 + tls_settings: + cert_file: server.crt + key_file: server.key + otlp/mtls: + protocols: + grpc: + client_ca_file: client.pem + endpoint: mysite.local:55690 + tls_settings: + cert_file: server.crt + key_file: server.key + otlp/notls: + protocols: + grpc: + endpoint: mysite.local:55690 +``` diff --git a/internal/otel_collector/config/configtls/configtls.go b/internal/otel_collector/config/configtls/configtls.go new file mode 100644 index 00000000000..2a27dc8117f --- /dev/null +++ b/internal/otel_collector/config/configtls/configtls.go @@ -0,0 +1,151 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configtls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "path/filepath" +) + +// TLSSetting exposes the common client and server TLS configurations. +// Note: Since there isn't anything specific to a server connection. Components +// with server connections should use TLSSetting. +type TLSSetting struct { + // Path to the CA cert. For a client this verifies the server certificate. + // For a server this verifies client certificates. If empty uses system root CA. + // (optional) + CAFile string `mapstructure:"ca_file"` + // Path to the TLS cert to use for TLS required connections. (optional) + CertFile string `mapstructure:"cert_file"` + // Path to the TLS key to use for TLS required connections. (optional) + KeyFile string `mapstructure:"key_file"` +} + +// TLSClientSetting contains TLS configurations that are specific to client +// connections in addition to the common configurations. This should be used by +// components configuring TLS client connections. +type TLSClientSetting struct { + TLSSetting `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // These are config options specific to client connections. + + // In gRPC when set to true, this is used to disable the client transport security. + // See https://godoc.org/google.golang.org/grpc#WithInsecure. + // In HTTP, this disables verifying the server's certificate chain and host name + // (InsecureSkipVerify in the tls Config). Please refer to + // https://godoc.org/crypto/tls#Config for more information. + // (optional, default false) + Insecure bool `mapstructure:"insecure"` + // InsecureSkipVerify will enable TLS but not verify the certificate. + InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"` + // ServerName requested by client for virtual hosting. + // This sets the ServerName in the TLSConfig. Please refer to + // https://godoc.org/crypto/tls#Config for more information. (optional) + ServerName string `mapstructure:"server_name_override"` +} + +// TLSServerSetting contains TLS configurations that are specific to server +// connections in addition to the common configurations. This should be used by +// components configuring TLS server connections. +type TLSServerSetting struct { + TLSSetting `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // These are config options specific to server connections. + + // Path to the TLS cert to use by the server to verify a client certificate. (optional) + // This sets the ClientCAs and ClientAuth to RequireAndVerifyClientCert in the TLSConfig. Please refer to + // https://godoc.org/crypto/tls#Config for more information. (optional) + ClientCAFile string `mapstructure:"client_ca_file"` +} + +// LoadTLSConfig loads TLS certificates and returns a tls.Config. +// This will set the RootCAs and Certificates of a tls.Config. +func (c TLSSetting) loadTLSConfig() (*tls.Config, error) { + // There is no need to load the System Certs for RootCAs because + // if the value is nil, it will default to checking against th System Certs. + var err error + var certPool *x509.CertPool + if len(c.CAFile) != 0 { + // setup user specified truststore + certPool, err = c.loadCert(c.CAFile) + if err != nil { + return nil, fmt.Errorf("failed to load CA CertPool: %w", err) + } + } + + if (c.CertFile == "" && c.KeyFile != "") || (c.CertFile != "" && c.KeyFile == "") { + return nil, fmt.Errorf("for auth via TLS, either both certificate and key must be supplied, or neither") + } + + var certificates []tls.Certificate + if c.CertFile != "" && c.KeyFile != "" { + tlsCert, err := tls.LoadX509KeyPair(filepath.Clean(c.CertFile), filepath.Clean(c.KeyFile)) + if err != nil { + return nil, fmt.Errorf("failed to load TLS cert and key: %w", err) + } + certificates = append(certificates, tlsCert) + } + + return &tls.Config{ + RootCAs: certPool, + Certificates: certificates, + }, nil +} + +func (c TLSSetting) loadCert(caPath string) (*x509.CertPool, error) { + caPEM, err := ioutil.ReadFile(filepath.Clean(caPath)) + if err != nil { + return nil, fmt.Errorf("failed to load CA %s: %w", caPath, err) + } + + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(caPEM) { + return nil, fmt.Errorf("failed to parse CA %s", caPath) + } + return certPool, nil +} + +func (c TLSClientSetting) LoadTLSConfig() (*tls.Config, error) { + if c.Insecure && c.CAFile == "" { + return nil, nil + } + + tlsCfg, err := c.TLSSetting.loadTLSConfig() + if err != nil { + return nil, fmt.Errorf("failed to load TLS config: %w", err) + } + tlsCfg.ServerName = c.ServerName + tlsCfg.InsecureSkipVerify = c.InsecureSkipVerify + return tlsCfg, nil +} + +func (c TLSServerSetting) LoadTLSConfig() (*tls.Config, error) { + tlsCfg, err := c.loadTLSConfig() + if err != nil { + return nil, fmt.Errorf("failed to load TLS config: %w", err) + } + if c.ClientCAFile != "" { + certPool, err := c.loadCert(c.ClientCAFile) + if err != nil { + return nil, fmt.Errorf("failed to load TLS config: failed to load client CA CertPool: %w", err) + } + tlsCfg.ClientCAs = certPool + tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert + } + return tlsCfg, nil +} diff --git a/internal/otel_collector/config/configtls/configtls_test.go b/internal/otel_collector/config/configtls/configtls_test.go new file mode 100644 index 00000000000..9ebc88fd213 --- /dev/null +++ b/internal/otel_collector/config/configtls/configtls_test.go @@ -0,0 +1,181 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configtls + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOptionsToConfig(t *testing.T) { + tests := []struct { + name string + options TLSSetting + expectError string + }{ + { + name: "should load system CA", + options: TLSSetting{CAFile: ""}, + }, + { + name: "should load custom CA", + options: TLSSetting{CAFile: "testdata/testCA.pem"}, + }, + { + name: "should fail with invalid CA file path", + options: TLSSetting{CAFile: "testdata/not/valid"}, + expectError: "failed to load CA", + }, + { + name: "should fail with invalid CA file content", + options: TLSSetting{CAFile: "testdata/testCA-bad.txt"}, + expectError: "failed to parse CA", + }, + { + name: "should load valid TLS settings", + options: TLSSetting{ + CAFile: "testdata/testCA.pem", + CertFile: "testdata/test-cert.pem", + KeyFile: "testdata/test-key.pem", + }, + }, + { + name: "should fail with missing TLS KeyFile", + options: TLSSetting{ + CAFile: "testdata/testCA.pem", + CertFile: "testdata/test-cert.pem", + }, + expectError: "both certificate and key must be supplied", + }, + { + name: "should fail with invalid TLS KeyFile", + options: TLSSetting{ + CAFile: "testdata/testCA.pem", + CertFile: "testdata/test-cert.pem", + KeyFile: "testdata/not/valid", + }, + expectError: "failed to load TLS cert and key", + }, + { + name: "should fail with missing TLS Cert", + options: TLSSetting{ + CAFile: "testdata/testCA.pem", + KeyFile: "testdata/test-key.pem", + }, + expectError: "both certificate and key must be supplied", + }, + { + name: "should fail with invalid TLS Cert", + options: TLSSetting{ + CAFile: "testdata/testCA.pem", + CertFile: "testdata/not/valid", + KeyFile: "testdata/test-key.pem", + }, + expectError: "failed to load TLS cert and key", + }, + { + name: "should fail with invalid TLS CA", + options: TLSSetting{ + CAFile: "testdata/not/valid", + }, + expectError: "failed to load CA", + }, + { + name: "should fail with invalid CA pool", + options: TLSSetting{ + CAFile: "testdata/testCA-bad.txt", + }, + expectError: "failed to parse CA", + }, + { + name: "should pass with valid CA pool", + options: TLSSetting{ + CAFile: "testdata/testCA.pem", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg, err := test.options.loadTLSConfig() + if test.expectError != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), test.expectError) + } else { + require.NoError(t, err) + assert.NotNil(t, cfg) + } + }) + } +} + +func TestLoadTLSClientConfigError(t *testing.T) { + tlsSetting := TLSClientSetting{ + TLSSetting: TLSSetting{ + CertFile: "doesnt/exist", + KeyFile: "doesnt/exist", + }, + } + _, err := tlsSetting.LoadTLSConfig() + assert.Error(t, err) +} + +func TestLoadTLSClientConfig(t *testing.T) { + tlsSetting := TLSClientSetting{ + Insecure: true, + } + tlsCfg, err := tlsSetting.LoadTLSConfig() + assert.NoError(t, err) + assert.Nil(t, tlsCfg) + + tlsSetting = TLSClientSetting{} + tlsCfg, err = tlsSetting.LoadTLSConfig() + assert.NoError(t, err) + assert.NotNil(t, tlsCfg) + + tlsSetting = TLSClientSetting{ + InsecureSkipVerify: true, + } + tlsCfg, err = tlsSetting.LoadTLSConfig() + assert.NoError(t, err) + assert.NotNil(t, tlsCfg) + assert.True(t, tlsCfg.InsecureSkipVerify) +} + +func TestLoadTLSServerConfigError(t *testing.T) { + tlsSetting := TLSServerSetting{ + TLSSetting: TLSSetting{ + CertFile: "doesnt/exist", + KeyFile: "doesnt/exist", + }, + } + _, err := tlsSetting.LoadTLSConfig() + assert.Error(t, err) + + tlsSetting = TLSServerSetting{ + ClientCAFile: "doesnt/exist", + } + _, err = tlsSetting.LoadTLSConfig() + assert.Error(t, err) +} + +func TestLoadTLSServerConfig(t *testing.T) { + tlsSetting := TLSServerSetting{} + tlsCfg, err := tlsSetting.LoadTLSConfig() + assert.NoError(t, err) + assert.NotNil(t, tlsCfg) +} diff --git a/internal/otel_collector/config/configtls/testdata/test-cert.pem b/internal/otel_collector/config/configtls/testdata/test-cert.pem new file mode 100644 index 00000000000..627628866fa --- /dev/null +++ b/internal/otel_collector/config/configtls/testdata/test-cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEHjCCAoagAwIBAgIQTUSsPHdq9Uhu2bMD29ThkDANBgkqhkiG9w0BAQsFADBd +MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExGTAXBgNVBAsMEHRyaXN0 +YW5AdHJpc3RhbnMxIDAeBgNVBAMMF21rY2VydCB0cmlzdGFuQHRyaXN0YW5zMB4X +DTE5MDYxMTA3NTA0NloXDTI5MDYxMTA3NTA0NlowRDEnMCUGA1UEChMebWtjZXJ0 +IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMRkwFwYDVQQLDBB0cmlzdGFuQHRyaXN0 +YW5zMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6zf2JlSdKTdiYDZV +i1yPnP65/CgqlxMflTP9N2P1W7F1SbvQgiGiSfUyc7NicffLGqqDbK3Q4hvANkRC +wOYc+nXZLL6IAxsZ/QBfud3GG2XhuETT2p84Wlqo55I3wFF+Efb89FRp+IiAy2gj +c275hmie6zDRYNJticmZwBIXfnYvwY66V8Y2jKEAjtf6BEmB8yPxWLhxdgY3FjWR +y3kRLfr6BhxVM2qYtl/gXbyGTFjAv7LgFQa/25OXRevs+VjBWFQiQ89b+YIZPpJB +y8y+02nsRLt9Oy9lWMq1/pEqySDV6T3rrw5rV7TLj2RGNkxbnjk+qmf5mWxYzO5X +QaBqeQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH +AwIwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBQbdcIL3c/Yr+lR9wLU2FLPJSfk +ZjAbBgNVHREEFDASghBjbGllbnQuamFlZ2VyLmlvMA0GCSqGSIb3DQEBCwUAA4IB +gQBx/tQKqGLQGv90TyQOdKFPPOQ0iU/pXrM0t1Gn55UuvSz6G66IufPQuV+MeW1B +CGcSm12QAjwIvFVPFiBurygQ9eYU/tZW1NCaTSoSRa8KzYMBuDlfqYdS3/7gq2+L +L3b9QZt4rLgaQp0StTlpgCZuKa6N4aK25HQpu+lZ/ZxL1cvLlTGtI2VEWrx9hZ9q +5ImLy063iSc/YqD51XR0LJTkjSdep4sBEGtl5V582ncZAGZQim90hiaPrf3TXVnN +HQuzJNE5pwS637nCcyzmXn07Wh4qcT5vWDmySeN9eDJjfrbM9il11mkGZ9JQYf8Z +S+1562KvxjVVlsegnXaR27tAGkJ40X/OZRC28jLEXIjManDhClZD3uwqlSRtb6/M +ux4+8kqL90msVRlZR5VnUCR5/rZr4ji07NMDVJijI99lRQ5rDbf7Z9CMUpLTXcfd +jJBweUKlFEe3HZ9BfZOU3tLbAdQa2/I420lFVo8mEdu6cpKQpW8fITDvl/71OpQu +FsI= +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/configtls/testdata/test-key.pem b/internal/otel_collector/config/configtls/testdata/test-key.pem new file mode 100644 index 00000000000..dc7766f9363 --- /dev/null +++ b/internal/otel_collector/config/configtls/testdata/test-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDrN/YmVJ0pN2Jg +NlWLXI+c/rn8KCqXEx+VM/03Y/VbsXVJu9CCIaJJ9TJzs2Jx98saqoNsrdDiG8A2 +RELA5hz6ddksvogDGxn9AF+53cYbZeG4RNPanzhaWqjnkjfAUX4R9vz0VGn4iIDL +aCNzbvmGaJ7rMNFg0m2JyZnAEhd+di/BjrpXxjaMoQCO1/oESYHzI/FYuHF2BjcW +NZHLeREt+voGHFUzapi2X+BdvIZMWMC/suAVBr/bk5dF6+z5WMFYVCJDz1v5ghk+ +kkHLzL7TaexEu307L2VYyrX+kSrJINXpPeuvDmtXtMuPZEY2TFueOT6qZ/mZbFjM +7ldBoGp5AgMBAAECgf854ouw4yHKAtcy1iw3H5A4Eneyli/k/c/H6ANonjDDX+h9 +PLsTSzOk/7JqxrpzUYeqCExPcnb1Ld8fe6zxy69V86p+WGUgXosGuBDWrL0UAP6L +WmTIaGZ11dm7I0CVE3jy8tVNS3jIsM8BP595yNWfPh/dwSXFrgNG5VXw7oLZm8Nd +q4+yybeRT/1dhlz+toV44x1GjfKkxqhnTPZvnyqvg8jYMVQmbsnUlvAyfRr3fh3g +zEnzuBW0KPPkNbMyL1Q3QU/8LVf4lQ37pI1887edJmlXtbEuh8QjTVDB/5oi7O5/ +2wxdGDTGIad4kXYG2vsuTVunZZq15BfMVyGkHoECgYEA9h1ROB6AfoxkykvQyJEb +1rOxQVz0tAgwzb25aThkSEXVZ6GgdZabgH4aArCGOEXrVt5hlDoDHC8ZEcyQ+yuF ++wFa2C6SorUkGnBJH9J9umWW+bOa5XigqgMHnpjM9yhNH34UnMSm+VarqczQhVx5 +QqIsbCsT+hbAkhwAgJo64kkCgYEA9KqbQ8YTRMc58n3juX8PIFYrOXsUGhWPG2jo +YoiUXgHSZDvxAsp6AtU2jUXjzjTCaF+h4zhxND3FD2yBLRt/Xx/GYXzmDf+Wx68B +4G0ZW4a+huoIEhsM6WGs7oT/sQxluMFb6G/rOaZEWDNzhYtVGNZTxnxCsd4eWj1j +9zy6RrECgYEA4qWTAyxLxr6Bny58ogfH7Evk4723+AdG8mFS2ww8hbYR1fKpM0C0 +CXuXdnybzjzNgl0e3YMjFBRncNXDehrVspbH0yfokABitBpNrQmKEVq201NMRSB2 +TLqnjK1IrB+oDmVslAYhgqMHSUK9kOLdJLj2UdLF/dxwEN3KtKPTsEkCgYEAhPPU +rY6MV/qfDZvFTL6z3JGWqYStVsNSYcWvSiQH49G/n4JHJIocpT9xhnFtKlfXMNqO +4SeBtK7AT/JZe8aOf4WHyuARL5gtOlNqhKckeW0OScgRHK2gZY4TaAXT4ETpXe2M +4RE4VLp6Nye2ZeJiGr4VBi3uHDOkcMsdcHOKkfECgYEAwEizw5kfhQ79bl9SwPbl +euE0wxUyEu+1lNqqAr6ty+BtfGufOxupzejNKghdpgB/bmuK77G8ikbDh6Ya6pQ1 +++Oes8NSFNiKq7pZOpjOeXRRo/OncBFKRDOX/i4ARWeJ/ZvjYz1fPyQuQiylaeDx +IYDJ4/DyVeyPiVrSQKJ5YLk= +-----END PRIVATE KEY----- diff --git a/internal/otel_collector/config/configtls/testdata/testCA-bad.txt b/internal/otel_collector/config/configtls/testdata/testCA-bad.txt new file mode 100644 index 00000000000..2f4aad65c7f --- /dev/null +++ b/internal/otel_collector/config/configtls/testdata/testCA-bad.txt @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE----- +bad certificate +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/configtls/testdata/testCA.pem b/internal/otel_collector/config/configtls/testdata/testCA.pem new file mode 100644 index 00000000000..0a986020542 --- /dev/null +++ b/internal/otel_collector/config/configtls/testdata/testCA.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICBzCCAXCgAwIBAgIQNkTaUtOczDHvL2YT/kqScTANBgkqhkiG9w0BAQsFADAX +MRUwEwYDVQQKEwxqYWdlcnRyYWNpbmcwHhcNMTkwMjA4MDYyODAyWhcNMTkwMjA4 +MDcyODAyWjAXMRUwEwYDVQQKEwxqYWdlcnRyYWNpbmcwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAMcOLYflHGbqC1f7+tbnsdfcpd0rEuX65+ab0WzelAgvo988 +yD+j7LDLPIE8IPk/tfqaETZ8h0LRUUTn8F2rW/wgrl/G8Onz0utog38N0elfTifG +Mu7GJCr/+aYM5xbQMDj4Brb4vhnkJF8UBe49fWILhIltUcm1SeKqVX3d1FvpAgMB +AAGjVDBSMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV +HRMBAf8EBTADAQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG +9w0BAQsFAAOBgQCreFjwpAn1HqJT812JOwoWKrt1NjOKGcz7pvIs1k3DfQVLH2aZ +iPKnCkzNgxMzQtwdgpAOXIAqXyNibvyOAv1C+3QSMLKbuPEHaIxlCuvl1suX/g25 +17x1o3Q64AnPCWOLpN2wjkfZqX7gZ84nsxpqb9Sbw1+2+kqX7dSZ3mfVxQ== +-----END CERTIFICATE----- diff --git a/internal/otel_collector/config/testdata/duplicate-exporter.yaml b/internal/otel_collector/config/testdata/duplicate-exporter.yaml new file mode 100644 index 00000000000..399be0b7bb5 --- /dev/null +++ b/internal/otel_collector/config/testdata/duplicate-exporter.yaml @@ -0,0 +1,13 @@ +receivers: + examplereceiver: +exporters: + exampleexporter/exp: + exampleexporter/ exp : +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [examplereceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/duplicate-extension.yaml b/internal/otel_collector/config/testdata/duplicate-extension.yaml new file mode 100644 index 00000000000..854d146723e --- /dev/null +++ b/internal/otel_collector/config/testdata/duplicate-extension.yaml @@ -0,0 +1,3 @@ +extensions: + exampleextension/ext: + exampleextension/ ext: diff --git a/internal/otel_collector/config/testdata/duplicate-pipeline.yaml b/internal/otel_collector/config/testdata/duplicate-pipeline.yaml new file mode 100644 index 00000000000..671991a79d5 --- /dev/null +++ b/internal/otel_collector/config/testdata/duplicate-pipeline.yaml @@ -0,0 +1,16 @@ +receivers: + examplereceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces/default: + receivers: [examplereceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] + traces/ default: + receivers: [examplereceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/duplicate-processor.yaml b/internal/otel_collector/config/testdata/duplicate-processor.yaml new file mode 100644 index 00000000000..5345cec5926 --- /dev/null +++ b/internal/otel_collector/config/testdata/duplicate-processor.yaml @@ -0,0 +1,13 @@ +receivers: + examplereceiver: +exporters: + exampleexporter: +processors: + exampleprocessor/ abc: + exampleprocessor/abc: +service: + pipelines: + traces: + receivers: [examplereceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/duplicate-receiver.yaml b/internal/otel_collector/config/testdata/duplicate-receiver.yaml new file mode 100644 index 00000000000..275054e6772 --- /dev/null +++ b/internal/otel_collector/config/testdata/duplicate-receiver.yaml @@ -0,0 +1,13 @@ +receivers: + examplereceiver/ 1: + examplereceiver/1: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [examplereceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/empty-config.yaml b/internal/otel_collector/config/testdata/empty-config.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/otel_collector/config/testdata/invalid-exporter-section.yaml b/internal/otel_collector/config/testdata/invalid-exporter-section.yaml new file mode 100644 index 00000000000..fa5e0257b9a --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-exporter-section.yaml @@ -0,0 +1,20 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: + unknown_section: exporter +extensions: + exampleextension: +service: + extensions: + - exampleextension + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter diff --git a/internal/otel_collector/config/testdata/invalid-exporter-sub-config.yaml b/internal/otel_collector/config/testdata/invalid-exporter-sub-config.yaml new file mode 100644 index 00000000000..ac4264869c3 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-exporter-sub-config.yaml @@ -0,0 +1,13 @@ +receivers: + multireceiver: +exporters: + exampleexporter: + tests +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [multireceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/invalid-extension-name.yaml b/internal/otel_collector/config/testdata/invalid-extension-name.yaml new file mode 100644 index 00000000000..8ed98292506 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-extension-name.yaml @@ -0,0 +1,15 @@ +extensions: + exampleextension: +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: +service: + extensions: [exampleextension, nosuchextension, and, another, three] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/invalid-extension-section.yaml b/internal/otel_collector/config/testdata/invalid-extension-section.yaml new file mode 100644 index 00000000000..1e0a809d9a3 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-extension-section.yaml @@ -0,0 +1,21 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: +extensions: + exampleextension: + unknown_section: + a_num: 2 +service: + extensions: + - examapleextension + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter diff --git a/internal/otel_collector/config/testdata/invalid-extension-sub-config.yaml b/internal/otel_collector/config/testdata/invalid-extension-sub-config.yaml new file mode 100644 index 00000000000..0859a71cfeb --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-extension-sub-config.yaml @@ -0,0 +1,16 @@ +extensions: + exampleextension: + tests +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: +service: + extensions: [exampleextension] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/invalid-pipeline-section.yaml b/internal/otel_collector/config/testdata/invalid-pipeline-section.yaml new file mode 100644 index 00000000000..760edb27db4 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-pipeline-section.yaml @@ -0,0 +1,20 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: +extensions: + exampleextension: +service: + extensions: + - exampleextension + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter + unknown_section: 1 diff --git a/internal/otel_collector/config/testdata/invalid-pipeline-sub-config.yaml b/internal/otel_collector/config/testdata/invalid-pipeline-sub-config.yaml new file mode 100644 index 00000000000..9b7dcee8072 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-pipeline-sub-config.yaml @@ -0,0 +1,9 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces diff --git a/internal/otel_collector/config/testdata/invalid-pipeline-type-and-name.yaml b/internal/otel_collector/config/testdata/invalid-pipeline-type-and-name.yaml new file mode 100644 index 00000000000..889dd6b1b51 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-pipeline-type-and-name.yaml @@ -0,0 +1,13 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: + +service: + pipelines: + /metrics: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/invalid-pipeline-type.yaml b/internal/otel_collector/config/testdata/invalid-pipeline-type.yaml new file mode 100644 index 00000000000..7c88405a947 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-pipeline-type.yaml @@ -0,0 +1,13 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: + +service: + pipelines: + wrongdatatype: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/invalid-processor-section.yaml b/internal/otel_collector/config/testdata/invalid-processor-section.yaml new file mode 100644 index 00000000000..01d2a086ed4 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-processor-section.yaml @@ -0,0 +1,21 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: + unknown_section: + a_num: 2 +exporters: + exampleexporter: +extensions: + exampleextension: +service: + extensions: + - examapleextension + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter diff --git a/internal/otel_collector/config/testdata/invalid-processor-sub-config.yaml b/internal/otel_collector/config/testdata/invalid-processor-sub-config.yaml new file mode 100644 index 00000000000..436290d616d --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-processor-sub-config.yaml @@ -0,0 +1,13 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: + tests +service: + pipelines: + traces: + receivers: [multireceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/invalid-receiver-name.yaml b/internal/otel_collector/config/testdata/invalid-receiver-name.yaml new file mode 100644 index 00000000000..ee71200b9ce --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-receiver-name.yaml @@ -0,0 +1,12 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [1,2,3] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/invalid-receiver-reference.yaml b/internal/otel_collector/config/testdata/invalid-receiver-reference.yaml new file mode 100644 index 00000000000..89288bae84c --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-receiver-reference.yaml @@ -0,0 +1,10 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [invalidreceivername] diff --git a/internal/otel_collector/config/testdata/invalid-receiver-section.yaml b/internal/otel_collector/config/testdata/invalid-receiver-section.yaml new file mode 100644 index 00000000000..558f17a4682 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-receiver-section.yaml @@ -0,0 +1,21 @@ +receivers: + examplereceiver: + unknown_section: + a_num: 2 +processors: + exampleprocessor: +exporters: + exampleexporter: +extensions: + exampleextension: +service: + extensions: + - examapleextension + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter diff --git a/internal/otel_collector/config/testdata/invalid-receiver-sub-config.yaml b/internal/otel_collector/config/testdata/invalid-receiver-sub-config.yaml new file mode 100644 index 00000000000..b7c567b2ce3 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-receiver-sub-config.yaml @@ -0,0 +1,13 @@ +receivers: + multireceiver: + tests +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [multireceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/invalid-sequence-value.yaml b/internal/otel_collector/config/testdata/invalid-sequence-value.yaml new file mode 100644 index 00000000000..7f7a0a385ee --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-sequence-value.yaml @@ -0,0 +1,14 @@ +receivers: + examplereceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: + examplereceiver: + some: config + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/invalid-service-extensions-value.yaml b/internal/otel_collector/config/testdata/invalid-service-extensions-value.yaml new file mode 100644 index 00000000000..00b123c1931 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-service-extensions-value.yaml @@ -0,0 +1,18 @@ +extensions: + exampleextension: +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: + +service: + extensions: + exampleextension: + error: true + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/invalid-service-section.yaml b/internal/otel_collector/config/testdata/invalid-service-section.yaml new file mode 100644 index 00000000000..6a3b8f92772 --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-service-section.yaml @@ -0,0 +1,21 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: +extensions: + exampleextension: +service: + extenstions: + - examapleextension + unknown_section: + a_num: 2 + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter diff --git a/internal/otel_collector/config/testdata/invalid-top-level-section.yaml b/internal/otel_collector/config/testdata/invalid-top-level-section.yaml new file mode 100644 index 00000000000..0b9819d17de --- /dev/null +++ b/internal/otel_collector/config/testdata/invalid-top-level-section.yaml @@ -0,0 +1,21 @@ +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: +extensions: + exampleextension: +service: + extenstions: + - examapleextension + pipelines: + traces: + receivers: + - examplereceiver + processors: + - exampleprocessor + exporters: + - exampleexporter +unknown_section: + a_num: 2 diff --git a/internal/otel_collector/config/testdata/metric-pipeline-cannot-have-processors.yaml b/internal/otel_collector/config/testdata/metric-pipeline-cannot-have-processors.yaml new file mode 100644 index 00000000000..28c865a1be9 --- /dev/null +++ b/internal/otel_collector/config/testdata/metric-pipeline-cannot-have-processors.yaml @@ -0,0 +1,12 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + metrics: + receivers: [multireceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/missing-all-sections.yaml b/internal/otel_collector/config/testdata/missing-all-sections.yaml new file mode 100644 index 00000000000..2eaee087e88 --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-all-sections.yaml @@ -0,0 +1,5 @@ +receivers: +exporters: +processors: +service: + pipeline: diff --git a/internal/otel_collector/config/testdata/missing-exporter-name-after-slash.yaml b/internal/otel_collector/config/testdata/missing-exporter-name-after-slash.yaml new file mode 100644 index 00000000000..ecb9517bcaf --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-exporter-name-after-slash.yaml @@ -0,0 +1,10 @@ +receivers: + multireceiver: +exporters: + exampleexporter/: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [somereceiver] diff --git a/internal/otel_collector/config/testdata/missing-exporters.yaml b/internal/otel_collector/config/testdata/missing-exporters.yaml new file mode 100644 index 00000000000..bcedbc83f27 --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-exporters.yaml @@ -0,0 +1,9 @@ +receivers: + multireceiver: +exporters: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [invalidreceivername] diff --git a/internal/otel_collector/config/testdata/missing-extension-type.yaml b/internal/otel_collector/config/testdata/missing-extension-type.yaml new file mode 100644 index 00000000000..14eca199345 --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-extension-type.yaml @@ -0,0 +1,2 @@ +extensions: + /exampleextension: diff --git a/internal/otel_collector/config/testdata/missing-pipelines.yaml b/internal/otel_collector/config/testdata/missing-pipelines.yaml new file mode 100644 index 00000000000..cf7f3ae1415 --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-pipelines.yaml @@ -0,0 +1,8 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: diff --git a/internal/otel_collector/config/testdata/missing-processor-type.yaml b/internal/otel_collector/config/testdata/missing-processor-type.yaml new file mode 100644 index 00000000000..d594ecdea3b --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-processor-type.yaml @@ -0,0 +1,10 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + /exampleprocessor: +service: + pipelines: + traces: + receivers: [somereceiver] diff --git a/internal/otel_collector/config/testdata/missing-processors.yaml b/internal/otel_collector/config/testdata/missing-processors.yaml new file mode 100644 index 00000000000..c3103ead3ea --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-processors.yaml @@ -0,0 +1,9 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: +service: + pipelines: + traces: + receivers: [invalidreceivername] diff --git a/internal/otel_collector/config/testdata/missing-receiver-type.yaml b/internal/otel_collector/config/testdata/missing-receiver-type.yaml new file mode 100644 index 00000000000..b05c26a28f7 --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-receiver-type.yaml @@ -0,0 +1,10 @@ +receivers: + /myreceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [somereceiver] diff --git a/internal/otel_collector/config/testdata/missing-receivers.yaml b/internal/otel_collector/config/testdata/missing-receivers.yaml new file mode 100644 index 00000000000..7a79d41a661 --- /dev/null +++ b/internal/otel_collector/config/testdata/missing-receivers.yaml @@ -0,0 +1,8 @@ +receivers: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: diff --git a/internal/otel_collector/config/testdata/multiproto-config.yaml b/internal/otel_collector/config/testdata/multiproto-config.yaml new file mode 100644 index 00000000000..23d4d801e59 --- /dev/null +++ b/internal/otel_collector/config/testdata/multiproto-config.yaml @@ -0,0 +1,26 @@ +receivers: + multireceiver: + multireceiver/myreceiver: + protocols: + http: + endpoint: "localhost:12345" + extra: "some string 1" + tcp: + endpoint: "0.0.0.0:4567" + extra: "some string 2" + +processors: + +exporters: + exampleexporter: + extra: "locahost:1010" + +service: + pipelines: + traces: + receivers: [multireceiver/myreceiver] + processors: [] + exporters: [exampleexporter] + metrics: + receivers: [multireceiver/myreceiver] + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/pipeline-exporter-not-exists.yaml b/internal/otel_collector/config/testdata/pipeline-exporter-not-exists.yaml new file mode 100644 index 00000000000..0a88af150f9 --- /dev/null +++ b/internal/otel_collector/config/testdata/pipeline-exporter-not-exists.yaml @@ -0,0 +1,11 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + metrics: + receivers: [multireceiver] + exporters: [nosuchexporter] diff --git a/internal/otel_collector/config/testdata/pipeline-must-have-exporter.yaml b/internal/otel_collector/config/testdata/pipeline-must-have-exporter.yaml new file mode 100644 index 00000000000..714506f34e0 --- /dev/null +++ b/internal/otel_collector/config/testdata/pipeline-must-have-exporter.yaml @@ -0,0 +1,11 @@ +receivers: + multireceiver: +exporters: + exampleexporter: + extra: "not present in the service pipeline" +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [multireceiver] diff --git a/internal/otel_collector/config/testdata/pipeline-must-have-exporter2.yaml b/internal/otel_collector/config/testdata/pipeline-must-have-exporter2.yaml new file mode 100644 index 00000000000..375216afe6d --- /dev/null +++ b/internal/otel_collector/config/testdata/pipeline-must-have-exporter2.yaml @@ -0,0 +1,11 @@ +receivers: + multireceiver: +exporters: + exampleexporter: + extra: "not present in the service pipeline" +processors: + exampleprocessor: +service: + pipelines: + metrics: + receivers: [multireceiver] diff --git a/internal/otel_collector/config/testdata/pipeline-must-have-receiver.yaml b/internal/otel_collector/config/testdata/pipeline-must-have-receiver.yaml new file mode 100644 index 00000000000..ff6904139c2 --- /dev/null +++ b/internal/otel_collector/config/testdata/pipeline-must-have-receiver.yaml @@ -0,0 +1,11 @@ +receivers: + examplereceiver: + extra: "not present in the service pipeline" +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/pipeline-must-have-receiver2.yaml b/internal/otel_collector/config/testdata/pipeline-must-have-receiver2.yaml new file mode 100644 index 00000000000..a4a99990a77 --- /dev/null +++ b/internal/otel_collector/config/testdata/pipeline-must-have-receiver2.yaml @@ -0,0 +1,11 @@ +receivers: + examplereceiver: + extra: "not present in the service pipeline" +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + metrics: + exporters: [exampleexporter] diff --git a/internal/otel_collector/config/testdata/pipeline-processor-not-exists.yaml b/internal/otel_collector/config/testdata/pipeline-processor-not-exists.yaml new file mode 100644 index 00000000000..634d9a3f4b4 --- /dev/null +++ b/internal/otel_collector/config/testdata/pipeline-processor-not-exists.yaml @@ -0,0 +1,15 @@ +receivers: + multireceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: + - multireceiver + processors: + - nosuchprocessor + exporters: + - exampleexporter diff --git a/internal/otel_collector/config/testdata/simple-config-with-all-env.yaml b/internal/otel_collector/config/testdata/simple-config-with-all-env.yaml new file mode 100644 index 00000000000..f3428d7eda4 --- /dev/null +++ b/internal/otel_collector/config/testdata/simple-config-with-all-env.yaml @@ -0,0 +1,51 @@ +receivers: + examplereceiver: + endpoint: "localhost:1234" + extra: "$RECEIVERS_EXAMPLERECEIVER_EXTRA" + extra_map: + recv.1: "$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_1" + recv.2: "$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2" + extra_list: + - "$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1" + - "$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_2" + + +processors: + exampleprocessor: + extra: "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA" + extra_map: + proc_1: "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1" + proc_2: "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_2" + extra_list: + - "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1" + - "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_2" + +exporters: + exampleexporter: + extra_int: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_INT}" + extra: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA}" + extra_map: + exp_1: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_1}" + exp_2: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_2}" + extra_list: + - "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1}" + - "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2}" + +extensions: + exampleextension: + extra: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA}" + extra_map: + ext-1: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1}" + ext-2: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2}" + extra_list: + - "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1}" + - "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_2}" + +service: + extensions: [exampleextension] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + diff --git a/internal/otel_collector/config/testdata/simple-config-with-escaped-env.yaml b/internal/otel_collector/config/testdata/simple-config-with-escaped-env.yaml new file mode 100644 index 00000000000..8870e6baf77 --- /dev/null +++ b/internal/otel_collector/config/testdata/simple-config-with-escaped-env.yaml @@ -0,0 +1,62 @@ +receivers: + examplereceiver: + endpoint: "localhost:1234" + extra: "$$RECEIVERS_EXAMPLERECEIVER_EXTRA" + extra_map: + # $$ -> escaped $ + recv.1: "$$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_1" + # $$$ -> escaped $ + substituted env var + recv.2: "$$$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2" + # $$$$ -> two escaped $ + recv.3: "$$$$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_3" + # escaped $ in the middle + recv.4: "some$${RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_4}text" + # two escaped $ + recv.5: "$${ONE}$${TWO}" + # trailing escaped $ + recv.6: "text$$" + # escaped $ alone + recv.7: "$$" + extra_list: + - "$$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1" + - "$$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_2" + + +processors: + exampleprocessor: + extra: "$$PROCESSORS_EXAMPLEPROCESSOR_EXTRA" + extra_map: + proc_1: "$$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1" + proc_2: "$$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_2" + extra_list: + - "$$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1" + - "$$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_2" + +exporters: + exampleexporter: + extra: "$${EXPORTERS_EXAMPLEEXPORTER_EXTRA}" + extra_map: + exp_1: "$${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_1}" + exp_2: "$${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_2}" + extra_list: + - "$${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1}" + - "$${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2}" + +extensions: + exampleextension: + extra: "$${EXTENSIONS_EXAMPLEEXTENSION_EXTRA}" + extra_map: + ext-1: "$${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1}" + ext-2: "$${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2}" + extra_list: + - "$${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1}" + - "$${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_2}" + +service: + extensions: [exampleextension] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + diff --git a/internal/otel_collector/config/testdata/simple-config-with-no-env.yaml b/internal/otel_collector/config/testdata/simple-config-with-no-env.yaml new file mode 100644 index 00000000000..f9e1d7708b3 --- /dev/null +++ b/internal/otel_collector/config/testdata/simple-config-with-no-env.yaml @@ -0,0 +1,51 @@ +receivers: + examplereceiver: + endpoint: "localhost:1234" + extra: "some receiver string" + extra_map: + recv.1: "some receiver map value_1" + recv.2: "some receiver map value_2" + extra_list: + - "some receiver list value_1" + - "some receiver list value_2" + + +processors: + exampleprocessor: + extra: "some processor string" + extra_map: + proc_1: "some processor map value_1" + proc_2: "some processor map value_2" + extra_list: + - "some processor list value_1" + - "some processor list value_2" + +exporters: + exampleexporter: + extra_int: 65 + extra: "some exporter string" + extra_map: + exp_1: "some exporter map value_1" + exp_2: "some exporter map value_2" + extra_list: + - "some exporter list value_1" + - "some exporter list value_2" + +extensions: + exampleextension: + extra: "some extension string" + extra_map: + ext-1: "some extension map value_1" + ext-2: "some extension map value_2" + extra_list: + - "some extension list value_1" + - "some extension list value_2" + +service: + extensions: [exampleextension] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + diff --git a/internal/otel_collector/config/testdata/simple-config-with-partial-env.yaml b/internal/otel_collector/config/testdata/simple-config-with-partial-env.yaml new file mode 100644 index 00000000000..7d3087b4a50 --- /dev/null +++ b/internal/otel_collector/config/testdata/simple-config-with-partial-env.yaml @@ -0,0 +1,51 @@ +receivers: + examplereceiver: + endpoint: "localhost:1234" + extra: "some receiver string" + extra_map: + recv.1: "some receiver map value_1" + recv.2: "some receiver map value_2" + extra_list: + - "some receiver list value_1" + - "some receiver list value_2" + + +processors: + exampleprocessor: + extra: "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA" + extra_map: + proc_1: "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1" + proc_2: "some processor map value_2" + extra_list: + - "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1" + - "some processor list value_2" + +exporters: + exampleexporter: + extra_int: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_INT}" + extra: "${EXPORTERS_EXAMPLEEXPORTER_EXTRA}" + extra_map: + exp_1: "some exporter map value_1" + exp_2: "some exporter map value_2" + extra_list: + - "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1}" + - "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2}" + +extensions: + exampleextension: + extra: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA}" + extra_map: + ext-1: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1}" + ext-2: "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2}" + extra_list: + - "some extension list value_1" + - "some extension list value_2" + +service: + extensions: [exampleextension] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + diff --git a/internal/otel_collector/config/testdata/unknown-exporter-type.yaml b/internal/otel_collector/config/testdata/unknown-exporter-type.yaml new file mode 100644 index 00000000000..deb74322a5d --- /dev/null +++ b/internal/otel_collector/config/testdata/unknown-exporter-type.yaml @@ -0,0 +1,12 @@ +receivers: + examplereceiver: +exporters: + nosuchexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: [multireceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/unknown-extension-type.yaml b/internal/otel_collector/config/testdata/unknown-extension-type.yaml new file mode 100644 index 00000000000..1ee2ca759c5 --- /dev/null +++ b/internal/otel_collector/config/testdata/unknown-extension-type.yaml @@ -0,0 +1,2 @@ +extensions: + nosuchextension: diff --git a/internal/otel_collector/config/testdata/unknown-processor-type.yaml b/internal/otel_collector/config/testdata/unknown-processor-type.yaml new file mode 100644 index 00000000000..02230c9d565 --- /dev/null +++ b/internal/otel_collector/config/testdata/unknown-processor-type.yaml @@ -0,0 +1,12 @@ +receivers: + examplereceiver: +exporters: + exampleexporter: +processors: + nosuchprocessor: +service: + pipelines: + traces: + receivers: [examplereceiver] + exporters: [exampleexporter] + processors: [exampleprocessor] diff --git a/internal/otel_collector/config/testdata/unknown-receiver-type.yaml b/internal/otel_collector/config/testdata/unknown-receiver-type.yaml new file mode 100644 index 00000000000..eef9a5a7948 --- /dev/null +++ b/internal/otel_collector/config/testdata/unknown-receiver-type.yaml @@ -0,0 +1,15 @@ +receivers: + nosuchreceiver: +exporters: + exampleexporter: +processors: + exampleprocessor: +service: + pipelines: + traces: + receivers: + - multireceiver + exporters: + - exampleexporter + processors: + - exampleprocessor diff --git a/internal/otel_collector/config/testdata/valid-config.yaml b/internal/otel_collector/config/testdata/valid-config.yaml new file mode 100644 index 00000000000..986f6ea1049 --- /dev/null +++ b/internal/otel_collector/config/testdata/valid-config.yaml @@ -0,0 +1,29 @@ +receivers: + examplereceiver: + examplereceiver/myreceiver: + endpoint: "localhost:12345" + extra: "some string" + +processors: + exampleprocessor: + +exporters: + exampleexporter/myexporter: + extra: "some export string 2" + exampleexporter: + +extensions: + exampleextension/0: + exampleextension/disabled: + extra: "not present in the service" + exampleextension/1: + extra: "some string" + +service: + extensions: [exampleextension/0, exampleextension/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + diff --git a/internal/otel_collector/consumer/consumer.go b/internal/otel_collector/consumer/consumer.go new file mode 100644 index 00000000000..aa867f62984 --- /dev/null +++ b/internal/otel_collector/consumer/consumer.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package consumer contains interfaces that receive and process consumerdata. +package consumer + +import ( + "context" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// MetricsConsumer is the new metrics consumer interface that receives pdata.MetricData, processes it +// as needed, and sends it to the next processing node if any or to the destination. +type MetricsConsumer interface { + ConsumeMetrics(ctx context.Context, md pdata.Metrics) error +} + +// TracesConsumer is an interface that receives pdata.Traces, processes it +// as needed, and sends it to the next processing node if any or to the destination. +type TracesConsumer interface { + // ConsumeTraces receives pdata.Traces for processing. + ConsumeTraces(ctx context.Context, td pdata.Traces) error +} + +// LogsConsumer is an interface that receives pdata.Logs, processes it +// as needed, and sends it to the next processing node if any or to the destination. +type LogsConsumer interface { + // ConsumeLogs receives pdata.Logs for processing. + ConsumeLogs(ctx context.Context, ld pdata.Logs) error +} diff --git a/internal/otel_collector/consumer/consumerdata/consumerdata.go b/internal/otel_collector/consumer/consumerdata/consumerdata.go new file mode 100644 index 00000000000..985f8909ea8 --- /dev/null +++ b/internal/otel_collector/consumer/consumerdata/consumerdata.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package consumerdata contains data structures that holds proto metrics/spans, node and resource. +package consumerdata + +import ( + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" +) + +// MetricsData is a struct that groups proto metrics with a unique node and a resource. +// Deprecated: use pdata.Metrics instead. +type MetricsData struct { + Node *commonpb.Node + Resource *resourcepb.Resource + Metrics []*metricspb.Metric +} + +// TraceData is a struct that groups proto spans with a unique node and a resource. +// Deprecated: use pdata.Traces instead. +type TraceData struct { + Node *commonpb.Node + Resource *resourcepb.Resource + Spans []*tracepb.Span + SourceFormat string +} diff --git a/internal/otel_collector/consumer/consumererror/partialerror.go b/internal/otel_collector/consumer/consumererror/partialerror.go new file mode 100644 index 00000000000..254af292021 --- /dev/null +++ b/internal/otel_collector/consumer/consumererror/partialerror.go @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumererror + +import "go.opentelemetry.io/collector/consumer/pdata" + +// PartialError can be used to signalize that a subset of received data failed to be processed or send. +// The preceding components in the pipeline can use this information for partial retries. +type PartialError struct { + error + failed pdata.Traces + failedLogs pdata.Logs + failedMetrics pdata.Metrics +} + +// PartialTracesError creates PartialError for failed traces. +// Use this error type only when a subset of received data set failed to be processed or sent. +func PartialTracesError(err error, failed pdata.Traces) error { + return PartialError{ + error: err, + failed: failed, + } +} + +// GetTraces returns failed traces. +func (err PartialError) GetTraces() pdata.Traces { + return err.failed +} + +// PartialLogsError creates PartialError for failed logs. +// Use this error type only when a subset of received data set failed to be processed or sent. +func PartialLogsError(err error, failedLogs pdata.Logs) error { + return PartialError{ + error: err, + failedLogs: failedLogs, + } +} + +// GetLogs returns failed logs. +func (err PartialError) GetLogs() pdata.Logs { + return err.failedLogs +} + +// PartialMetricsError creates PartialError for failed metrics. +// Use this error type only when a subset of received data set failed to be processed or sent. +func PartialMetricsError(err error, failedMetrics pdata.Metrics) error { + return PartialError{ + error: err, + failedMetrics: failedMetrics, + } +} + +// GetMetrics returns failed metrics. +func (err PartialError) GetMetrics() pdata.Metrics { + return err.failedMetrics +} diff --git a/internal/otel_collector/consumer/consumererror/partialerror_test.go b/internal/otel_collector/consumer/consumererror/partialerror_test.go new file mode 100644 index 00000000000..54d19c1c755 --- /dev/null +++ b/internal/otel_collector/consumer/consumererror/partialerror_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumererror + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestPartialError(t *testing.T) { + td := testdata.GenerateTraceDataOneSpan() + err := fmt.Errorf("some error") + partialErr := PartialTracesError(err, td) + assert.Equal(t, err.Error(), partialErr.Error()) + assert.Equal(t, td, partialErr.(PartialError).failed) +} + +func TestPartialErrorLogs(t *testing.T) { + td := testdata.GenerateLogDataOneLog() + err := fmt.Errorf("some error") + partialErr := PartialLogsError(err, td) + assert.Equal(t, err.Error(), partialErr.Error()) + assert.Equal(t, td, partialErr.(PartialError).failedLogs) +} + +func TestPartialErrorMetrics(t *testing.T) { + td := testdata.GenerateMetricsOneMetric() + err := fmt.Errorf("some error") + partialErr := PartialMetricsError(err, td) + assert.Equal(t, err.Error(), partialErr.Error()) + assert.Equal(t, td, partialErr.(PartialError).failedMetrics) +} diff --git a/internal/otel_collector/consumer/consumererror/partialscrapeerror.go b/internal/otel_collector/consumer/consumererror/partialscrapeerror.go new file mode 100644 index 00000000000..a3980d3c0bf --- /dev/null +++ b/internal/otel_collector/consumer/consumererror/partialscrapeerror.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumererror + +import "errors" + +// PartialScrapeError can be used to signalize that a subset of metrics were failed +// to be scraped +type PartialScrapeError struct { + error + Failed int +} + +// NewPartialScrapeError creates PartialScrapeError for failed metrics. +// Use this error type only when a subset of data was failed to be scraped. +func NewPartialScrapeError(err error, failed int) error { + return PartialScrapeError{ + error: err, + Failed: failed, + } +} + +// IsPartialScrapeError checks if an error was wrapped with PartialScrapeError. +func IsPartialScrapeError(err error) bool { + if err == nil { + return false + } + + var partialScrapeErr PartialScrapeError + return errors.As(err, &partialScrapeErr) +} diff --git a/internal/otel_collector/consumer/consumererror/partialscrapeerror_test.go b/internal/otel_collector/consumer/consumererror/partialscrapeerror_test.go new file mode 100644 index 00000000000..58d6cae5bea --- /dev/null +++ b/internal/otel_collector/consumer/consumererror/partialscrapeerror_test.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumererror + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPartialScrapeError(t *testing.T) { + failed := 2 + err := fmt.Errorf("some error") + partialErr := NewPartialScrapeError(err, failed) + assert.Equal(t, err.Error(), partialErr.Error()) + assert.Equal(t, failed, partialErr.(PartialScrapeError).Failed) +} + +func TestIsPartialScrapeError(t *testing.T) { + err := errors.New("testError") + require.False(t, IsPartialScrapeError(err)) + + err = NewPartialScrapeError(err, 2) + require.True(t, IsPartialScrapeError(err)) +} diff --git a/internal/otel_collector/consumer/consumererror/permanenterror.go b/internal/otel_collector/consumer/consumererror/permanenterror.go new file mode 100644 index 00000000000..e4d0950596e --- /dev/null +++ b/internal/otel_collector/consumer/consumererror/permanenterror.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package consumererror provides wrappers to easily classify errors. This allows +// appropriate action by error handlers without the need to know each individual +// error type/instance. +package consumererror + +// permanent is an error that will be always returned if its source +// receives the same inputs. +type permanent struct { + err error +} + +// Permanent wraps an error to indicate that it is a permanent error, i.e.: an +// error that will be always returned if its source receives the same inputs. +func Permanent(err error) error { + return permanent{err: err} +} + +func (p permanent) Error() string { + return "Permanent error: " + p.err.Error() +} + +// IsPermanent checks if an error was wrapped with the Permanent function, that +// is used to indicate that a given error will always be returned in the case +// that its sources receives the same input. +func IsPermanent(err error) bool { + if err != nil { + _, isPermanent := err.(permanent) + return isPermanent + } + return false +} diff --git a/internal/otel_collector/consumer/consumererror/permanenterror_test.go b/internal/otel_collector/consumer/consumererror/permanenterror_test.go new file mode 100644 index 00000000000..8674825fc57 --- /dev/null +++ b/internal/otel_collector/consumer/consumererror/permanenterror_test.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumererror + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPermanent(t *testing.T) { + err := errors.New("testError") + require.False(t, IsPermanent(err)) + + err = Permanent(err) + require.True(t, IsPermanent(err)) +} + +func TestIsPermanent_NilError(t *testing.T) { + var err error + require.False(t, IsPermanent(err)) +} diff --git a/internal/otel_collector/consumer/consumertest/nop.go b/internal/otel_collector/consumer/consumertest/nop.go new file mode 100644 index 00000000000..74af9d90cb7 --- /dev/null +++ b/internal/otel_collector/consumer/consumertest/nop.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumertest + +import ( + "context" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +var ( + nopInstance = &nopConsumer{} +) + +type nopConsumer struct{} + +func (nc *nopConsumer) ConsumeTraces(context.Context, pdata.Traces) error { + return nil +} + +func (nc *nopConsumer) ConsumeMetrics(context.Context, pdata.Metrics) error { + return nil +} + +func (nc *nopConsumer) ConsumeLogs(context.Context, pdata.Logs) error { + return nil +} + +// NewTracesNop creates an TraceConsumer that just drops the received data. +func NewTracesNop() consumer.TracesConsumer { + return nopInstance +} + +// NewMetricsNop creates an MetricsConsumer that just drops the received data. +func NewMetricsNop() consumer.MetricsConsumer { + return nopInstance +} + +// NewLogsNop creates an LogsConsumer that just drops the received data. +func NewLogsNop() consumer.LogsConsumer { + return nopInstance +} diff --git a/internal/otel_collector/consumer/consumertest/nop_test.go b/internal/otel_collector/consumer/consumertest/nop_test.go new file mode 100644 index 00000000000..852879658db --- /dev/null +++ b/internal/otel_collector/consumer/consumertest/nop_test.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumertest + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestTracesNop(t *testing.T) { + nt := NewTracesNop() + require.NotNil(t, nt) + require.NoError(t, nt.ConsumeTraces(context.Background(), pdata.NewTraces())) +} + +func TestMetricsNop(t *testing.T) { + nm := NewMetricsNop() + require.NotNil(t, nm) + require.NoError(t, nm.ConsumeMetrics(context.Background(), pdata.NewMetrics())) +} + +func TestLogsNop(t *testing.T) { + nl := NewLogsNop() + require.NotNil(t, nl) + require.NoError(t, nl.ConsumeLogs(context.Background(), pdata.NewLogs())) +} diff --git a/internal/otel_collector/consumer/consumertest/sink.go b/internal/otel_collector/consumer/consumertest/sink.go new file mode 100644 index 00000000000..d35257289ee --- /dev/null +++ b/internal/otel_collector/consumer/consumertest/sink.go @@ -0,0 +1,183 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumertest + +import ( + "context" + "sync" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +type baseErrorConsumer struct { + mu sync.Mutex + consumeError error // to be returned by ConsumeTraces, if set +} + +// SetConsumeError sets an error that will be returned by the Consume function. +func (bec *baseErrorConsumer) SetConsumeError(err error) { + bec.mu.Lock() + defer bec.mu.Unlock() + bec.consumeError = err +} + +// TracesSink acts as a trace receiver for use in tests. +type TracesSink struct { + baseErrorConsumer + traces []pdata.Traces + spansCount int +} + +var _ consumer.TracesConsumer = (*TracesSink)(nil) + +// ConsumeTraceData stores traces for tests. +func (ste *TracesSink) ConsumeTraces(_ context.Context, td pdata.Traces) error { + ste.mu.Lock() + defer ste.mu.Unlock() + + if ste.consumeError != nil { + return ste.consumeError + } + + ste.traces = append(ste.traces, td) + ste.spansCount += td.SpanCount() + + return nil +} + +// AllTraces returns the traces sent to the test sink. +func (ste *TracesSink) AllTraces() []pdata.Traces { + ste.mu.Lock() + defer ste.mu.Unlock() + + copyTraces := make([]pdata.Traces, len(ste.traces)) + copy(copyTraces, ste.traces) + return copyTraces +} + +// SpansCount return the number of spans sent to the test sing. +func (ste *TracesSink) SpansCount() int { + ste.mu.Lock() + defer ste.mu.Unlock() + return ste.spansCount +} + +// Reset deletes any existing metrics. +func (ste *TracesSink) Reset() { + ste.mu.Lock() + defer ste.mu.Unlock() + + ste.traces = nil + ste.spansCount = 0 +} + +// MetricsSink acts as a metrics receiver for use in tests. +type MetricsSink struct { + baseErrorConsumer + metrics []pdata.Metrics + metricsCount int +} + +var _ consumer.MetricsConsumer = (*MetricsSink)(nil) + +// ConsumeMetricsData stores traces for tests. +func (sme *MetricsSink) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + sme.mu.Lock() + defer sme.mu.Unlock() + if sme.consumeError != nil { + return sme.consumeError + } + + sme.metrics = append(sme.metrics, md) + sme.metricsCount += md.MetricCount() + + return nil +} + +// AllMetrics returns the metrics sent to the test sink. +func (sme *MetricsSink) AllMetrics() []pdata.Metrics { + sme.mu.Lock() + defer sme.mu.Unlock() + + copyMetrics := make([]pdata.Metrics, len(sme.metrics)) + copy(copyMetrics, sme.metrics) + return copyMetrics +} + +// MetricsCount return the number of metrics sent to the test sing. +func (sme *MetricsSink) MetricsCount() int { + sme.mu.Lock() + defer sme.mu.Unlock() + return sme.metricsCount +} + +// Reset deletes any existing metrics. +func (sme *MetricsSink) Reset() { + sme.mu.Lock() + defer sme.mu.Unlock() + + sme.metrics = nil + sme.metricsCount = 0 +} + +// LogsSink acts as a metrics receiver for use in tests. +type LogsSink struct { + baseErrorConsumer + logs []pdata.Logs + logRecordsCount int +} + +var _ consumer.LogsConsumer = (*LogsSink)(nil) + +// ConsumeLogData stores traces for tests. +func (sle *LogsSink) ConsumeLogs(_ context.Context, ld pdata.Logs) error { + sle.mu.Lock() + defer sle.mu.Unlock() + if sle.consumeError != nil { + return sle.consumeError + } + + sle.logs = append(sle.logs, ld) + sle.logRecordsCount += ld.LogRecordCount() + + return nil +} + +// AllLog returns the metrics sent to the test sink. +func (sle *LogsSink) AllLogs() []pdata.Logs { + sle.mu.Lock() + defer sle.mu.Unlock() + + copyLogs := make([]pdata.Logs, len(sle.logs)) + copy(copyLogs, sle.logs) + return copyLogs +} + +// LogRecordsCount return the number of log records sent to the test sing. +func (sle *LogsSink) LogRecordsCount() int { + sle.mu.Lock() + defer sle.mu.Unlock() + return sle.logRecordsCount +} + +// Reset deletes any existing logs. +func (sle *LogsSink) Reset() { + sle.mu.Lock() + defer sle.mu.Unlock() + + sle.logs = nil + sle.logRecordsCount = 0 +} diff --git a/internal/otel_collector/consumer/consumertest/sink_test.go b/internal/otel_collector/consumer/consumertest/sink_test.go new file mode 100644 index 00000000000..17990885f62 --- /dev/null +++ b/internal/otel_collector/consumer/consumertest/sink_test.go @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package consumertest + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestTracesSink(t *testing.T) { + sink := new(TracesSink) + td := testdata.GenerateTraceDataOneSpan() + want := make([]pdata.Traces, 0, 7) + for i := 0; i < 7; i++ { + require.NoError(t, sink.ConsumeTraces(context.Background(), td)) + want = append(want, td) + } + assert.Equal(t, want, sink.AllTraces()) + assert.Equal(t, len(want), sink.SpansCount()) + sink.Reset() + assert.Equal(t, 0, len(sink.AllTraces())) + assert.Equal(t, 0, sink.SpansCount()) +} + +func TestTracesSink_Error(t *testing.T) { + sink := new(TracesSink) + sink.SetConsumeError(errors.New("my error")) + td := testdata.GenerateTraceDataOneSpan() + require.Error(t, sink.ConsumeTraces(context.Background(), td)) + assert.Len(t, sink.AllTraces(), 0) + assert.Equal(t, 0, sink.SpansCount()) +} + +func TestMetricsSink(t *testing.T) { + sink := new(MetricsSink) + md := testdata.GenerateMetricsOneMetric() + want := make([]pdata.Metrics, 0, 7) + for i := 0; i < 7; i++ { + require.NoError(t, sink.ConsumeMetrics(context.Background(), md)) + want = append(want, md) + } + assert.Equal(t, want, sink.AllMetrics()) + assert.Equal(t, len(want), sink.MetricsCount()) + sink.Reset() + assert.Equal(t, 0, len(sink.AllMetrics())) + assert.Equal(t, 0, sink.MetricsCount()) +} + +func TestMetricsSink_Error(t *testing.T) { + sink := new(MetricsSink) + sink.SetConsumeError(errors.New("my error")) + md := testdata.GenerateMetricsOneMetric() + require.Error(t, sink.ConsumeMetrics(context.Background(), md)) + assert.Len(t, sink.AllMetrics(), 0) + assert.Equal(t, 0, sink.MetricsCount()) +} + +func TestLogsSink(t *testing.T) { + sink := new(LogsSink) + md := testdata.GenerateLogDataOneLogNoResource() + want := make([]pdata.Logs, 0, 7) + for i := 0; i < 7; i++ { + require.NoError(t, sink.ConsumeLogs(context.Background(), md)) + want = append(want, md) + } + assert.Equal(t, want, sink.AllLogs()) + assert.Equal(t, len(want), sink.LogRecordsCount()) + sink.Reset() + assert.Equal(t, 0, len(sink.AllLogs())) + assert.Equal(t, 0, sink.LogRecordsCount()) +} + +func TestLogsSink_Error(t *testing.T) { + sink := new(LogsSink) + sink.SetConsumeError(errors.New("my error")) + ld := testdata.GenerateLogDataOneLogNoResource() + require.Error(t, sink.ConsumeLogs(context.Background(), ld)) + assert.Len(t, sink.AllLogs(), 0) + assert.Equal(t, 0, sink.LogRecordsCount()) +} diff --git a/internal/otel_collector/consumer/pdata/common.go b/internal/otel_collector/consumer/pdata/common.go new file mode 100644 index 00000000000..7bd75dd8ecb --- /dev/null +++ b/internal/otel_collector/consumer/pdata/common.go @@ -0,0 +1,790 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +// This file contains data structures that are common for all telemetry types, +// such as timestamps, attributes, etc. + +import ( + "sort" + "time" + + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" +) + +// TimestampUnixNano is a time specified as UNIX Epoch time in nanoseconds since +// 00:00:00 UTC on 1 January 1970. +type TimestampUnixNano uint64 + +func (ts TimestampUnixNano) String() string { + return time.Unix(0, int64(ts)).String() +} + +// AttributeValueType specifies the type of AttributeValue. +type AttributeValueType int + +const ( + AttributeValueNULL AttributeValueType = iota + AttributeValueSTRING + AttributeValueINT + AttributeValueDOUBLE + AttributeValueBOOL + AttributeValueMAP + AttributeValueARRAY +) + +func (avt AttributeValueType) String() string { + switch avt { + case AttributeValueNULL: + return "NULL" + case AttributeValueSTRING: + return "STRING" + case AttributeValueBOOL: + return "BOOL" + case AttributeValueINT: + return "INT" + case AttributeValueDOUBLE: + return "DOUBLE" + case AttributeValueMAP: + return "MAP" + case AttributeValueARRAY: + return "ARRAY" + } + return "" +} + +// AttributeValue represents a value of an attribute. Typically used in AttributeMap. +// Must use one of NewAttributeValue+ functions below to create new instances. +// +// Intended to be passed by value since internally it is just a pointer to actual +// value representation. For the same reason passing by value and calling setters +// will modify the original, e.g.: +// +// function f1(val AttributeValue) { val.SetIntVal(234) } +// function f2() { +// v := NewAttributeValueString("a string") +// f1(v) +// _ := v.Type() // this will return AttributeValueINT +// } +// +// Important: zero-initialized instance is not valid for use. All AttributeValue functions bellow must +// be called only on instances that are created via NewAttributeValue+ functions. +type AttributeValue struct { + orig *otlpcommon.AnyValue +} + +func newAttributeValue(orig *otlpcommon.AnyValue) AttributeValue { + return AttributeValue{orig} +} + +// NewAttributeValueNull creates a new AttributeValue with a null value. +func NewAttributeValueNull() AttributeValue { + orig := &otlpcommon.AnyValue{} + return AttributeValue{orig: orig} +} + +// Deprecated: Use NewAttributeValueNull() +func NewAttributeValue() AttributeValue { + return NewAttributeValueNull() +} + +// NewAttributeValueString creates a new AttributeValue with the given string value. +func NewAttributeValueString(v string) AttributeValue { + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: v}} + return AttributeValue{orig: orig} +} + +// NewAttributeValueInt creates a new AttributeValue with the given int64 value. +func NewAttributeValueInt(v int64) AttributeValue { + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_IntValue{IntValue: v}} + return AttributeValue{orig: orig} +} + +// NewAttributeValueDouble creates a new AttributeValue with the given float64 value. +func NewAttributeValueDouble(v float64) AttributeValue { + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_DoubleValue{DoubleValue: v}} + return AttributeValue{orig: orig} +} + +// NewAttributeValueBool creates a new AttributeValue with the given bool value. +func NewAttributeValueBool(v bool) AttributeValue { + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_BoolValue{BoolValue: v}} + return AttributeValue{orig: orig} +} + +// NewAttributeValueMap creates a new AttributeValue of map type. +func NewAttributeValueMap() AttributeValue { + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: &otlpcommon.KeyValueList{}}} + return AttributeValue{orig: orig} +} + +// NewAttributeValueArray creates a new AttributeValue of array type. +func NewAttributeValueArray() AttributeValue { + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: &otlpcommon.ArrayValue{}}} + return AttributeValue{orig: orig} +} + +// Type returns the type of the value for this AttributeValue. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) Type() AttributeValueType { + if a.orig.Value == nil { + return AttributeValueNULL + } + switch a.orig.Value.(type) { + case *otlpcommon.AnyValue_StringValue: + return AttributeValueSTRING + case *otlpcommon.AnyValue_BoolValue: + return AttributeValueBOOL + case *otlpcommon.AnyValue_IntValue: + return AttributeValueINT + case *otlpcommon.AnyValue_DoubleValue: + return AttributeValueDOUBLE + case *otlpcommon.AnyValue_KvlistValue: + return AttributeValueMAP + case *otlpcommon.AnyValue_ArrayValue: + return AttributeValueARRAY + } + return AttributeValueNULL +} + +// StringVal returns the string value associated with this AttributeValue. +// If the Type() is not AttributeValueSTRING then returns empty string. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) StringVal() string { + return a.orig.GetStringValue() +} + +// IntVal returns the int64 value associated with this AttributeValue. +// If the Type() is not AttributeValueINT then returns int64(0). +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) IntVal() int64 { + return a.orig.GetIntValue() +} + +// DoubleVal returns the float64 value associated with this AttributeValue. +// If the Type() is not AttributeValueDOUBLE then returns float64(0). +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) DoubleVal() float64 { + return a.orig.GetDoubleValue() +} + +// BoolVal returns the bool value associated with this AttributeValue. +// If the Type() is not AttributeValueBOOL then returns false. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) BoolVal() bool { + return a.orig.GetBoolValue() +} + +// MapVal returns the map value associated with this AttributeValue. +// If the Type() is not AttributeValueMAP then returns an empty map. Note that modifying +// such empty map has no effect on this AttributeValue. +// +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) MapVal() AttributeMap { + kvlist := a.orig.GetKvlistValue() + if kvlist == nil { + return NewAttributeMap() + } + return newAttributeMap(&kvlist.Values) +} + +// ArrayVal returns the array value associated with this AttributeValue. +// If the Type() is not AttributeValueARRAY then returns an empty array. Note that modifying +// such empty array has no effect on this AttributeValue. +// +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) ArrayVal() AnyValueArray { + arr := a.orig.GetArrayValue() + if arr == nil { + return NewAnyValueArray() + } + return newAnyValueArray(&arr.Values) +} + +// SetStringVal replaces the string value associated with this AttributeValue, +// it also changes the type to be AttributeValueSTRING. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) SetStringVal(v string) { + a.orig.Value = &otlpcommon.AnyValue_StringValue{StringValue: v} +} + +// SetIntVal replaces the int64 value associated with this AttributeValue, +// it also changes the type to be AttributeValueINT. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) SetIntVal(v int64) { + a.orig.Value = &otlpcommon.AnyValue_IntValue{IntValue: v} +} + +// SetDoubleVal replaces the float64 value associated with this AttributeValue, +// it also changes the type to be AttributeValueDOUBLE. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) SetDoubleVal(v float64) { + a.orig.Value = &otlpcommon.AnyValue_DoubleValue{DoubleValue: v} +} + +// SetBoolVal replaces the bool value associated with this AttributeValue, +// it also changes the type to be AttributeValueBOOL. +// Calling this function on zero-initialized AttributeValue will cause a panic. +func (a AttributeValue) SetBoolVal(v bool) { + a.orig.Value = &otlpcommon.AnyValue_BoolValue{BoolValue: v} +} + +// copyTo copies the value to AnyValue. Will panic if dest is nil. +func (a AttributeValue) copyTo(dest *otlpcommon.AnyValue) { + switch v := a.orig.Value.(type) { + case *otlpcommon.AnyValue_KvlistValue: + kv, ok := dest.Value.(*otlpcommon.AnyValue_KvlistValue) + if !ok { + kv = &otlpcommon.AnyValue_KvlistValue{KvlistValue: &otlpcommon.KeyValueList{}} + dest.Value = kv + } + if v.KvlistValue == nil { + kv.KvlistValue = nil + return + } + // Deep copy to dest. + newAttributeMap(&v.KvlistValue.Values).CopyTo(newAttributeMap(&kv.KvlistValue.Values)) + case *otlpcommon.AnyValue_ArrayValue: + av, ok := dest.Value.(*otlpcommon.AnyValue_ArrayValue) + if !ok { + av = &otlpcommon.AnyValue_ArrayValue{ArrayValue: &otlpcommon.ArrayValue{}} + dest.Value = av + } + if v.ArrayValue == nil { + av.ArrayValue = nil + return + } + // Deep copy to dest. + newAnyValueArray(&v.ArrayValue.Values).CopyTo(newAnyValueArray(&av.ArrayValue.Values)) + default: + // Primitive immutable type, no need for deep copy. + dest.Value = a.orig.Value + } +} + +func (a AttributeValue) CopyTo(dest AttributeValue) { + a.copyTo(dest.orig) +} + +// Equal checks for equality, it returns true if the objects are equal otherwise false. +func (a AttributeValue) Equal(av AttributeValue) bool { + if a.orig.Value == nil || av.orig.Value == nil { + return a.orig.Value == av.orig.Value + } + + switch v := a.orig.Value.(type) { + case *otlpcommon.AnyValue_StringValue: + return v.StringValue == av.orig.GetStringValue() + case *otlpcommon.AnyValue_BoolValue: + return v.BoolValue == av.orig.GetBoolValue() + case *otlpcommon.AnyValue_IntValue: + return v.IntValue == av.orig.GetIntValue() + case *otlpcommon.AnyValue_DoubleValue: + return v.DoubleValue == av.orig.GetDoubleValue() + } + // TODO: handle MAP and ARRAY data types. + return false +} + +func newAttributeKeyValueString(k string, v string) otlpcommon.KeyValue { + orig := otlpcommon.KeyValue{Key: k} + akv := AttributeValue{&orig.Value} + akv.SetStringVal(v) + return orig +} + +func newAttributeKeyValueInt(k string, v int64) otlpcommon.KeyValue { + orig := otlpcommon.KeyValue{Key: k} + akv := AttributeValue{&orig.Value} + akv.SetIntVal(v) + return orig +} + +func newAttributeKeyValueDouble(k string, v float64) otlpcommon.KeyValue { + orig := otlpcommon.KeyValue{Key: k} + akv := AttributeValue{&orig.Value} + akv.SetDoubleVal(v) + return orig +} + +func newAttributeKeyValueBool(k string, v bool) otlpcommon.KeyValue { + orig := otlpcommon.KeyValue{Key: k} + akv := AttributeValue{&orig.Value} + akv.SetBoolVal(v) + return orig +} + +func newAttributeKeyValueNull(k string) otlpcommon.KeyValue { + orig := otlpcommon.KeyValue{Key: k} + return orig +} + +func newAttributeKeyValue(k string, av AttributeValue) otlpcommon.KeyValue { + orig := otlpcommon.KeyValue{Key: k} + av.copyTo(&orig.Value) + return orig +} + +// AttributeMap stores a map of attribute keys to values. +type AttributeMap struct { + orig *[]otlpcommon.KeyValue +} + +// NewAttributeMap creates a AttributeMap with 0 elements. +func NewAttributeMap() AttributeMap { + orig := []otlpcommon.KeyValue(nil) + return AttributeMap{&orig} +} + +func newAttributeMap(orig *[]otlpcommon.KeyValue) AttributeMap { + return AttributeMap{orig} +} + +// InitFromMap overwrites the entire AttributeMap and reconstructs the AttributeMap +// with values from the given map[string]string. +// +// Returns the same instance to allow nicer code like: +// assert.EqualValues(t, NewAttributeMap().InitFromMap(map[string]AttributeValue{...}), actual) +func (am AttributeMap) InitFromMap(attrMap map[string]AttributeValue) AttributeMap { + if len(attrMap) == 0 { + *am.orig = []otlpcommon.KeyValue(nil) + return am + } + origs := make([]otlpcommon.KeyValue, len(attrMap)) + ix := 0 + for k, v := range attrMap { + origs[ix].Key = k + v.copyTo(&origs[ix].Value) + ix++ + } + *am.orig = origs + return am +} + +// InitEmptyWithCapacity constructs an empty AttributeMap with predefined slice capacity. +func (am AttributeMap) InitEmptyWithCapacity(cap int) { + if cap == 0 { + *am.orig = []otlpcommon.KeyValue(nil) + return + } + *am.orig = make([]otlpcommon.KeyValue, 0, cap) +} + +// Get returns the AttributeValue associated with the key and true. Returned +// AttributeValue is not a copy, it is a reference to the value stored in this map. +// It is allowed to modify the returned value using AttributeValue.Set* functions. +// Such modification will be applied to the value stored in this map. +// +// If the key does not exist returns an invalid instance of the KeyValue and false. +// Calling any functions on the returned invalid instance will cause a panic. +func (am AttributeMap) Get(key string) (AttributeValue, bool) { + for i := range *am.orig { + akv := &(*am.orig)[i] + if akv.Key == key { + return AttributeValue{&akv.Value}, true + } + } + return AttributeValue{nil}, false +} + +// Delete deletes the entry associated with the key and returns true if the key +// was present in the map, otherwise returns false. +func (am AttributeMap) Delete(key string) bool { + for i := range *am.orig { + akv := &(*am.orig)[i] + if akv.Key == key { + *akv = (*am.orig)[len(*am.orig)-1] + *am.orig = (*am.orig)[:len(*am.orig)-1] + return true + } + } + return false +} + +// Insert adds the AttributeValue to the map when the key does not exist. +// No action is applied to the map where the key already exists. +// +// Calling this function with a zero-initialized AttributeValue struct will cause a panic. +// +// Important: this function should not be used if the caller has access to +// the raw value to avoid an extra allocation. +func (am AttributeMap) Insert(k string, v AttributeValue) { + if _, existing := am.Get(k); !existing { + *am.orig = append(*am.orig, newAttributeKeyValue(k, v)) + } +} + +// InsertNull adds a null Value to the map when the key does not exist. +// No action is applied to the map where the key already exists. +func (am AttributeMap) InsertNull(k string) { + if _, existing := am.Get(k); !existing { + *am.orig = append(*am.orig, newAttributeKeyValueNull(k)) + } +} + +// InsertString adds the string Value to the map when the key does not exist. +// No action is applied to the map where the key already exists. +func (am AttributeMap) InsertString(k string, v string) { + if _, existing := am.Get(k); !existing { + *am.orig = append(*am.orig, newAttributeKeyValueString(k, v)) + } +} + +// InsertInt adds the int Value to the map when the key does not exist. +// No action is applied to the map where the key already exists. +func (am AttributeMap) InsertInt(k string, v int64) { + if _, existing := am.Get(k); !existing { + *am.orig = append(*am.orig, newAttributeKeyValueInt(k, v)) + } +} + +// InsertDouble adds the double Value to the map when the key does not exist. +// No action is applied to the map where the key already exists. +func (am AttributeMap) InsertDouble(k string, v float64) { + if _, existing := am.Get(k); !existing { + *am.orig = append(*am.orig, newAttributeKeyValueDouble(k, v)) + } +} + +// InsertBool adds the bool Value to the map when the key does not exist. +// No action is applied to the map where the key already exists. +func (am AttributeMap) InsertBool(k string, v bool) { + if _, existing := am.Get(k); !existing { + *am.orig = append(*am.orig, newAttributeKeyValueBool(k, v)) + } +} + +// Update updates an existing AttributeValue with a value. +// No action is applied to the map where the key does not exist. +// +// Calling this function with a zero-initialized AttributeValue struct will cause a panic. +// +// Important: this function should not be used if the caller has access to +// the raw value to avoid an extra allocation. +func (am AttributeMap) Update(k string, v AttributeValue) { + if av, existing := am.Get(k); existing { + v.copyTo(av.orig) + } +} + +// UpdateString updates an existing string Value with a value. +// No action is applied to the map where the key does not exist. +func (am AttributeMap) UpdateString(k string, v string) { + if av, existing := am.Get(k); existing { + av.SetStringVal(v) + } +} + +// UpdateInt updates an existing int Value with a value. +// No action is applied to the map where the key does not exist. +func (am AttributeMap) UpdateInt(k string, v int64) { + if av, existing := am.Get(k); existing { + av.SetIntVal(v) + } +} + +// UpdateDouble updates an existing double Value with a value. +// No action is applied to the map where the key does not exist. +func (am AttributeMap) UpdateDouble(k string, v float64) { + if av, existing := am.Get(k); existing { + av.SetDoubleVal(v) + } +} + +// UpdateBool updates an existing bool Value with a value. +// No action is applied to the map where the key does not exist. +func (am AttributeMap) UpdateBool(k string, v bool) { + if av, existing := am.Get(k); existing { + av.SetBoolVal(v) + } +} + +// Upsert performs the Insert or Update action. The AttributeValue is +// insert to the map that did not originally have the key. The key/value is +// updated to the map where the key already existed. +// +// Calling this function with a zero-initialized AttributeValue struct will cause a panic. +// +// Important: this function should not be used if the caller has access to +// the raw value to avoid an extra allocation. +func (am AttributeMap) Upsert(k string, v AttributeValue) { + if av, existing := am.Get(k); existing { + v.copyTo(av.orig) + } else { + *am.orig = append(*am.orig, newAttributeKeyValue(k, v)) + } +} + +// UpsertString performs the Insert or Update action. The AttributeValue is +// insert to the map that did not originally have the key. The key/value is +// updated to the map where the key already existed. +func (am AttributeMap) UpsertString(k string, v string) { + if av, existing := am.Get(k); existing { + av.SetStringVal(v) + } else { + *am.orig = append(*am.orig, newAttributeKeyValueString(k, v)) + } +} + +// UpsertInt performs the Insert or Update action. The int Value is +// insert to the map that did not originally have the key. The key/value is +// updated to the map where the key already existed. +func (am AttributeMap) UpsertInt(k string, v int64) { + if av, existing := am.Get(k); existing { + av.SetIntVal(v) + } else { + *am.orig = append(*am.orig, newAttributeKeyValueInt(k, v)) + } +} + +// UpsertDouble performs the Insert or Update action. The double Value is +// insert to the map that did not originally have the key. The key/value is +// updated to the map where the key already existed. +func (am AttributeMap) UpsertDouble(k string, v float64) { + if av, existing := am.Get(k); existing { + av.SetDoubleVal(v) + } else { + *am.orig = append(*am.orig, newAttributeKeyValueDouble(k, v)) + } +} + +// UpsertBool performs the Insert or Update action. The bool Value is +// insert to the map that did not originally have the key. The key/value is +// updated to the map where the key already existed. +func (am AttributeMap) UpsertBool(k string, v bool) { + if av, existing := am.Get(k); existing { + av.SetBoolVal(v) + } else { + *am.orig = append(*am.orig, newAttributeKeyValueBool(k, v)) + } +} + +// Sort sorts the entries in the AttributeMap so two instances can be compared. +// Returns the same instance to allow nicer code like: +// assert.EqualValues(t, expected.Sort(), actual.Sort()) +func (am AttributeMap) Sort() AttributeMap { + // Intention is to move the nil values at the end. + sort.SliceStable(*am.orig, func(i, j int) bool { + return (*am.orig)[i].Key < (*am.orig)[j].Key + }) + return am +} + +// Len returns the length of this map. +// +// Because the AttributeMap is represented internally by a slice of pointers, and the data are comping from the wire, +// it is possible that when iterating using "ForEach" to get access to fewer elements because nil elements are skipped. +func (am AttributeMap) Len() int { + return len(*am.orig) +} + +// ForEach iterates over the every elements in the map by calling the provided func. +// +// Example: +// +// it := sm.ForEach(func(k string, v AttributeValue) { +// ... +// }) +func (am AttributeMap) ForEach(f func(k string, v AttributeValue)) { + for i := range *am.orig { + kv := &(*am.orig)[i] + f(kv.Key, AttributeValue{&kv.Value}) + } +} + +// CopyTo copies all elements from the current map to the dest. +func (am AttributeMap) CopyTo(dest AttributeMap) { + newLen := len(*am.orig) + oldCap := cap(*dest.orig) + if newLen <= oldCap { + // New slice fits in existing slice, no need to reallocate. + *dest.orig = (*dest.orig)[:newLen:oldCap] + for i := range *am.orig { + akv := &(*am.orig)[i] + destAkv := &(*dest.orig)[i] + destAkv.Key = akv.Key + AttributeValue{&akv.Value}.copyTo(&destAkv.Value) + } + return + } + + // New slice is bigger than exist slice. Allocate new space. + origs := make([]otlpcommon.KeyValue, len(*am.orig)) + for i := range *am.orig { + akv := &(*am.orig)[i] + origs[i].Key = akv.Key + AttributeValue{&akv.Value}.copyTo(&origs[i].Value) + } + *dest.orig = origs +} + +// StringMap stores a map of attribute keys to values. +type StringMap struct { + orig *[]otlpcommon.StringKeyValue +} + +// NewStringMap creates a StringMap with 0 elements. +func NewStringMap() StringMap { + orig := []otlpcommon.StringKeyValue(nil) + return StringMap{&orig} +} + +func newStringMap(orig *[]otlpcommon.StringKeyValue) StringMap { + return StringMap{orig} +} + +// InitFromMap overwrites the entire StringMap and reconstructs the StringMap +// with values from the given map[string]string. +// +// Returns the same instance to allow nicer code like: +// assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{...}), actual) +func (sm StringMap) InitFromMap(attrMap map[string]string) StringMap { + if len(attrMap) == 0 { + *sm.orig = []otlpcommon.StringKeyValue(nil) + return sm + } + origs := make([]otlpcommon.StringKeyValue, len(attrMap)) + ix := 0 + for k, v := range attrMap { + origs[ix].Key = k + origs[ix].Value = v + ix++ + } + *sm.orig = origs + return sm +} + +// InitEmptyWithCapacity constructs an empty StringMap with predefined slice capacity. +func (sm StringMap) InitEmptyWithCapacity(cap int) { + if cap == 0 { + *sm.orig = []otlpcommon.StringKeyValue(nil) + return + } + *sm.orig = make([]otlpcommon.StringKeyValue, 0, cap) +} + +// Get returns the StringValue associated with the key and true, +// otherwise an invalid instance of the StringKeyValue and false. +// Calling any functions on the returned invalid instance will cause a panic. +func (sm StringMap) Get(k string) (string, bool) { + skv, found := sm.get(k) + // GetValue handles the case where skv is nil. + return skv.GetValue(), found +} + +// Delete deletes the entry associated with the key and returns true if the key +// was present in the map, otherwise returns false. +func (sm StringMap) Delete(k string) bool { + for i := range *sm.orig { + skv := &(*sm.orig)[i] + if skv.Key == k { + (*sm.orig)[i] = (*sm.orig)[len(*sm.orig)-1] + *sm.orig = (*sm.orig)[:len(*sm.orig)-1] + return true + } + } + return false +} + +// Insert adds the string value to the map when the key does not exist. +// No action is applied to the map where the key already exists. +func (sm StringMap) Insert(k, v string) { + if _, existing := sm.Get(k); !existing { + *sm.orig = append(*sm.orig, newStringKeyValue(k, v)) + } +} + +// Update updates an existing string value with a value. +// No action is applied to the map where the key does not exist. +func (sm StringMap) Update(k, v string) { + if skv, existing := sm.get(k); existing { + skv.Value = v + } +} + +// Upsert performs the Insert or Update action. The string value is +// insert to the map that did not originally have the key. The key/value is +// updated to the map where the key already existed. +func (sm StringMap) Upsert(k, v string) { + if skv, existing := sm.get(k); existing { + skv.Value = v + } else { + *sm.orig = append(*sm.orig, newStringKeyValue(k, v)) + } +} + +// Len returns the length of this map. +// +// Because the AttributeMap is represented internally by a slice of pointers, and the data are comping from the wire, +// it is possible that when iterating using "ForEach" to get access to fewer elements because nil elements are skipped. +func (sm StringMap) Len() int { + return len(*sm.orig) +} + +// ForEach iterates over the every elements in the map by calling the provided func. +// +// Example: +// +// it := sm.ForEach(func(k string, v StringValue) { +// ... +// }) +func (sm StringMap) ForEach(f func(k string, v string)) { + for i := range *sm.orig { + skv := &(*sm.orig)[i] + f(skv.Key, skv.Value) + } +} + +// CopyTo copies all elements from the current map to the dest. +func (sm StringMap) CopyTo(dest StringMap) { + newLen := len(*sm.orig) + oldCap := cap(*dest.orig) + if newLen <= oldCap { + *dest.orig = (*dest.orig)[:newLen:oldCap] + } else { + *dest.orig = make([]otlpcommon.StringKeyValue, newLen) + } + + for i := range *sm.orig { + skv := &(*sm.orig)[i] + (*dest.orig)[i].Key = skv.Key + (*dest.orig)[i].Value = skv.Value + } +} + +func (sm StringMap) get(k string) (*otlpcommon.StringKeyValue, bool) { + for i := range *sm.orig { + skv := &(*sm.orig)[i] + if skv.Key == k { + return skv, true + } + } + return nil, false +} + +// Sort sorts the entries in the StringMap so two instances can be compared. +// Returns the same instance to allow nicer code like: +// assert.EqualValues(t, expected.Sort(), actual.Sort()) +func (sm StringMap) Sort() StringMap { + sort.SliceStable(*sm.orig, func(i, j int) bool { + // Intention is to move the nil values at the end. + return (*sm.orig)[i].Key < (*sm.orig)[j].Key + }) + return sm +} + +func newStringKeyValue(k, v string) otlpcommon.StringKeyValue { + return otlpcommon.StringKeyValue{Key: k, Value: v} +} diff --git a/internal/otel_collector/consumer/pdata/common_test.go b/internal/otel_collector/consumer/pdata/common_test.go new file mode 100644 index 00000000000..06f217ba94c --- /dev/null +++ b/internal/otel_collector/consumer/pdata/common_test.go @@ -0,0 +1,1131 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "encoding/json" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" +) + +func TestAttributeValue(t *testing.T) { + v := NewAttributeValueString("abc") + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "abc", v.StringVal()) + + v = NewAttributeValueInt(123) + assert.EqualValues(t, AttributeValueINT, v.Type()) + assert.EqualValues(t, 123, v.IntVal()) + + v = NewAttributeValueDouble(3.4) + assert.EqualValues(t, AttributeValueDOUBLE, v.Type()) + assert.EqualValues(t, 3.4, v.DoubleVal()) + + v = NewAttributeValueBool(true) + assert.EqualValues(t, AttributeValueBOOL, v.Type()) + assert.True(t, v.BoolVal()) + + v = NewAttributeValueNull() + assert.EqualValues(t, AttributeValueNULL, v.Type()) + + v.SetStringVal("abc") + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "abc", v.StringVal()) + + v.SetIntVal(123) + assert.EqualValues(t, AttributeValueINT, v.Type()) + assert.EqualValues(t, 123, v.IntVal()) + + v.SetDoubleVal(3.4) + assert.EqualValues(t, AttributeValueDOUBLE, v.Type()) + assert.EqualValues(t, 3.4, v.DoubleVal()) + + v.SetBoolVal(true) + assert.EqualValues(t, AttributeValueBOOL, v.Type()) + assert.True(t, v.BoolVal()) +} + +func TestAttributeValueType(t *testing.T) { + assert.EqualValues(t, "NULL", AttributeValueNULL.String()) + assert.EqualValues(t, "STRING", AttributeValueSTRING.String()) + assert.EqualValues(t, "BOOL", AttributeValueBOOL.String()) + assert.EqualValues(t, "INT", AttributeValueINT.String()) + assert.EqualValues(t, "DOUBLE", AttributeValueDOUBLE.String()) + assert.EqualValues(t, "MAP", AttributeValueMAP.String()) + assert.EqualValues(t, "ARRAY", AttributeValueARRAY.String()) +} + +func fromVal(v interface{}) AttributeValue { + switch val := v.(type) { + case string: + return NewAttributeValueString(val) + case int: + return NewAttributeValueInt(int64(val)) + case float64: + return NewAttributeValueDouble(val) + case map[string]interface{}: + return fromMap(val) + case []interface{}: + return fromArray(val) + } + panic("data type is not supported in fromVal()") +} + +func fromMap(v map[string]interface{}) AttributeValue { + av := NewAttributeValueMap() + m := av.MapVal() + for k, v := range v { + m.Insert(k, fromVal(v)) + } + m.Sort() + return av +} + +func fromJSONMap(jsonStr string) AttributeValue { + var src map[string]interface{} + err := json.Unmarshal([]byte(jsonStr), &src) + if err != nil { + panic("Invalid input jsonStr:" + jsonStr) + } + return fromMap(src) +} + +func assertMapJSON(t *testing.T, expectedJSON string, actualMap AttributeValue) { + assert.EqualValues(t, fromJSONMap(expectedJSON).MapVal(), actualMap.MapVal().Sort()) +} + +func TestAttributeValueMap(t *testing.T) { + m1 := NewAttributeValueMap() + assert.EqualValues(t, fromJSONMap(`{}`), m1) + assert.EqualValues(t, AttributeValueMAP, m1.Type()) + assert.EqualValues(t, NewAttributeMap(), m1.MapVal()) + assert.EqualValues(t, 0, m1.MapVal().Len()) + + m1.MapVal().InsertDouble("double_key", 123) + assertMapJSON(t, `{"double_key":123}`, m1) + assert.EqualValues(t, 1, m1.MapVal().Len()) + + v, exists := m1.MapVal().Get("double_key") + require.True(t, exists) + assert.EqualValues(t, AttributeValueDOUBLE, v.Type()) + assert.EqualValues(t, 123, v.DoubleVal()) + + // Create a second map. + m2 := NewAttributeValueMap() + assertMapJSON(t, `{}`, m2) + assert.EqualValues(t, 0, m2.MapVal().Len()) + + // Modify the source map that was inserted. + m2.MapVal().UpsertString("key_in_child", "somestr") + assertMapJSON(t, `{"key_in_child": "somestr"}`, m2) + assert.EqualValues(t, 1, m2.MapVal().Len()) + + // Insert the second map as a child. This should perform a deep copy. + m1.MapVal().Insert("child_map", m2) + assertMapJSON(t, `{"double_key":123, "child_map": {"key_in_child": "somestr"}}`, m1) + assert.EqualValues(t, 2, m1.MapVal().Len()) + + // Check that the map was correctly copied. + childMap, exists := m1.MapVal().Get("child_map") + require.True(t, exists) + assert.EqualValues(t, AttributeValueMAP, childMap.Type()) + assert.EqualValues(t, 1, childMap.MapVal().Len()) + + v, exists = childMap.MapVal().Get("key_in_child") + require.True(t, exists) + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "somestr", v.StringVal()) + + // Modify the source map m2 that was inserted into m1. + m2.MapVal().UpdateString("key_in_child", "somestr2") + assertMapJSON(t, `{"key_in_child": "somestr2"}`, m2) + assert.EqualValues(t, 1, m2.MapVal().Len()) + + // The child map inside m1 should not be modified. + assertMapJSON(t, `{"double_key":123, "child_map": {"key_in_child": "somestr"}}`, m1) + childMap, exists = m1.MapVal().Get("child_map") + require.True(t, exists) + v, exists = childMap.MapVal().Get("key_in_child") + require.True(t, exists) + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "somestr", v.StringVal()) + + // Now modify the inserted map (not the source) + childMap.MapVal().UpdateString("key_in_child", "somestr3") + assertMapJSON(t, `{"double_key":123, "child_map": {"key_in_child": "somestr3"}}`, m1) + assert.EqualValues(t, 1, childMap.MapVal().Len()) + + v, exists = childMap.MapVal().Get("key_in_child") + require.True(t, exists) + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "somestr3", v.StringVal()) + + // The source child map should not be modified. + v, exists = m2.MapVal().Get("key_in_child") + require.True(t, exists) + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "somestr2", v.StringVal()) + + deleted := m1.MapVal().Delete("double_key") + assert.True(t, deleted) + assertMapJSON(t, `{"child_map": {"key_in_child": "somestr3"}}`, m1) + assert.EqualValues(t, 1, m1.MapVal().Len()) + _, exists = m1.MapVal().Get("double_key") + assert.False(t, exists) + + deleted = m1.MapVal().Delete("child_map") + assert.True(t, deleted) + assert.EqualValues(t, 0, m1.MapVal().Len()) + _, exists = m1.MapVal().Get("child_map") + assert.False(t, exists) + + // Test nil KvlistValue case for MapVal() func. + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: nil}} + m1 = AttributeValue{orig: orig} + assert.EqualValues(t, NewAttributeMap(), m1.MapVal()) +} + +func TestNilOrigSetAttributeValue(t *testing.T) { + av := NewAttributeValueNull() + av.SetStringVal("abc") + assert.EqualValues(t, "abc", av.StringVal()) + + av = NewAttributeValueNull() + av.SetIntVal(123) + assert.EqualValues(t, 123, av.IntVal()) + + av = NewAttributeValueNull() + av.SetBoolVal(true) + assert.True(t, av.BoolVal()) + + av = NewAttributeValueNull() + av.SetDoubleVal(1.23) + assert.EqualValues(t, 1.23, av.DoubleVal()) +} + +func TestAttributeValueEqual(t *testing.T) { + av1 := NewAttributeValueNull() + av2 := NewAttributeValueNull() + assert.True(t, av1.Equal(av2)) + + av2 = NewAttributeValueString("abc") + assert.False(t, av1.Equal(av2)) + assert.False(t, av2.Equal(av1)) + + av1 = NewAttributeValueString("abc") + assert.True(t, av1.Equal(av2)) + + av2 = NewAttributeValueString("edf") + assert.False(t, av1.Equal(av2)) + + av2 = NewAttributeValueInt(123) + assert.False(t, av1.Equal(av2)) + assert.False(t, av2.Equal(av1)) + + av1 = NewAttributeValueInt(234) + assert.False(t, av1.Equal(av2)) + + av1 = NewAttributeValueInt(123) + assert.True(t, av1.Equal(av2)) + + av2 = NewAttributeValueDouble(123) + assert.False(t, av1.Equal(av2)) + assert.False(t, av2.Equal(av1)) + + av1 = NewAttributeValueDouble(234) + assert.False(t, av1.Equal(av2)) + + av1 = NewAttributeValueDouble(123) + assert.True(t, av1.Equal(av2)) + + av2 = NewAttributeValueBool(true) + assert.False(t, av1.Equal(av2)) + assert.False(t, av2.Equal(av1)) + + av1 = NewAttributeValueBool(true) + assert.True(t, av1.Equal(av2)) + + av1 = NewAttributeValueBool(false) + assert.False(t, av1.Equal(av2)) +} + +func TestNilAttributeMap(t *testing.T) { + assert.EqualValues(t, 0, NewAttributeMap().Len()) + + val, exist := NewAttributeMap().Get("test_key") + assert.False(t, exist) + assert.EqualValues(t, AttributeValue{nil}, val) + + insertMap := NewAttributeMap() + insertMap.Insert("k", NewAttributeValueString("v")) + assert.EqualValues(t, generateTestAttributeMap(), insertMap) + + insertMapString := NewAttributeMap() + insertMapString.InsertString("k", "v") + assert.EqualValues(t, generateTestAttributeMap(), insertMapString) + + insertMapNull := NewAttributeMap() + insertMapNull.InsertNull("k") + assert.EqualValues(t, generateTestNullAttributeMap(), insertMapNull) + + insertMapInt := NewAttributeMap() + insertMapInt.InsertInt("k", 123) + assert.EqualValues(t, generateTestIntAttributeMap(), insertMapInt) + + insertMapDouble := NewAttributeMap() + insertMapDouble.InsertDouble("k", 12.3) + assert.EqualValues(t, generateTestDoubleAttributeMap(), insertMapDouble) + + insertMapBool := NewAttributeMap() + insertMapBool.InsertBool("k", true) + assert.EqualValues(t, generateTestBoolAttributeMap(), insertMapBool) + + updateMap := NewAttributeMap() + updateMap.Update("k", NewAttributeValueString("v")) + assert.EqualValues(t, NewAttributeMap(), updateMap) + + updateMapString := NewAttributeMap() + updateMapString.UpdateString("k", "v") + assert.EqualValues(t, NewAttributeMap(), updateMapString) + + updateMapInt := NewAttributeMap() + updateMapInt.UpdateInt("k", 123) + assert.EqualValues(t, NewAttributeMap(), updateMapInt) + + updateMapDouble := NewAttributeMap() + updateMapDouble.UpdateDouble("k", 12.3) + assert.EqualValues(t, NewAttributeMap(), updateMapDouble) + + updateMapBool := NewAttributeMap() + updateMapBool.UpdateBool("k", true) + assert.EqualValues(t, NewAttributeMap(), updateMapBool) + + upsertMap := NewAttributeMap() + upsertMap.Upsert("k", NewAttributeValueString("v")) + assert.EqualValues(t, generateTestAttributeMap(), upsertMap) + + upsertMapString := NewAttributeMap() + upsertMapString.UpsertString("k", "v") + assert.EqualValues(t, generateTestAttributeMap(), upsertMapString) + + upsertMapInt := NewAttributeMap() + upsertMapInt.UpsertInt("k", 123) + assert.EqualValues(t, generateTestIntAttributeMap(), upsertMapInt) + + upsertMapDouble := NewAttributeMap() + upsertMapDouble.UpsertDouble("k", 12.3) + assert.EqualValues(t, generateTestDoubleAttributeMap(), upsertMapDouble) + + upsertMapBool := NewAttributeMap() + upsertMapBool.UpsertBool("k", true) + assert.EqualValues(t, generateTestBoolAttributeMap(), upsertMapBool) + + deleteMap := NewAttributeMap() + assert.False(t, deleteMap.Delete("k")) + assert.EqualValues(t, NewAttributeMap(), deleteMap) + + // Test Sort + assert.EqualValues(t, NewAttributeMap(), NewAttributeMap().Sort()) +} + +func TestAttributeMapWithEmpty(t *testing.T) { + origWithNil := []otlpcommon.KeyValue{ + {}, + { + Key: "test_key", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "test_value"}}, + }, + { + Key: "test_key2", + Value: otlpcommon.AnyValue{Value: nil}, + }, + } + sm := AttributeMap{ + orig: &origWithNil, + } + val, exist := sm.Get("test_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "test_value", val.StringVal()) + + val, exist = sm.Get("test_key2") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueNULL, val.Type()) + assert.EqualValues(t, "", val.StringVal()) + + sm.Insert("other_key", NewAttributeValueString("other_value")) + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "other_value", val.StringVal()) + + sm.InsertString("other_key_string", "other_value") + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "other_value", val.StringVal()) + + sm.InsertInt("other_key_int", 123) + val, exist = sm.Get("other_key_int") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueINT, val.Type()) + assert.EqualValues(t, 123, val.IntVal()) + + sm.InsertDouble("other_key_double", 1.23) + val, exist = sm.Get("other_key_double") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueDOUBLE, val.Type()) + assert.EqualValues(t, 1.23, val.DoubleVal()) + + sm.InsertBool("other_key_bool", true) + val, exist = sm.Get("other_key_bool") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueBOOL, val.Type()) + assert.True(t, val.BoolVal()) + + sm.Update("other_key", NewAttributeValueString("yet_another_value")) + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "yet_another_value", val.StringVal()) + + sm.UpdateString("other_key_string", "yet_another_value") + val, exist = sm.Get("other_key_string") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "yet_another_value", val.StringVal()) + + sm.UpdateInt("other_key_int", 456) + val, exist = sm.Get("other_key_int") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueINT, val.Type()) + assert.EqualValues(t, 456, val.IntVal()) + + sm.UpdateDouble("other_key_double", 4.56) + val, exist = sm.Get("other_key_double") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueDOUBLE, val.Type()) + assert.EqualValues(t, 4.56, val.DoubleVal()) + + sm.UpdateBool("other_key_bool", false) + val, exist = sm.Get("other_key_bool") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueBOOL, val.Type()) + assert.False(t, val.BoolVal()) + + sm.Upsert("other_key", NewAttributeValueString("other_value")) + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "other_value", val.StringVal()) + + sm.UpsertString("other_key_string", "other_value") + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "other_value", val.StringVal()) + + sm.UpsertInt("other_key_int", 123) + val, exist = sm.Get("other_key_int") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueINT, val.Type()) + assert.EqualValues(t, 123, val.IntVal()) + + sm.UpsertDouble("other_key_double", 1.23) + val, exist = sm.Get("other_key_double") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueDOUBLE, val.Type()) + assert.EqualValues(t, 1.23, val.DoubleVal()) + + sm.UpsertBool("other_key_bool", true) + val, exist = sm.Get("other_key_bool") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueBOOL, val.Type()) + assert.True(t, val.BoolVal()) + + sm.Upsert("yet_another_key", NewAttributeValueString("yet_another_value")) + val, exist = sm.Get("yet_another_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "yet_another_value", val.StringVal()) + + sm.UpsertString("yet_another_key_string", "yet_another_value") + val, exist = sm.Get("yet_another_key_string") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "yet_another_value", val.StringVal()) + + sm.UpsertInt("yet_another_key_int", 456) + val, exist = sm.Get("yet_another_key_int") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueINT, val.Type()) + assert.EqualValues(t, 456, val.IntVal()) + + sm.UpsertDouble("yet_another_key_double", 4.56) + val, exist = sm.Get("yet_another_key_double") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueDOUBLE, val.Type()) + assert.EqualValues(t, 4.56, val.DoubleVal()) + + sm.UpsertBool("yet_another_key_bool", false) + val, exist = sm.Get("yet_another_key_bool") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueBOOL, val.Type()) + assert.False(t, val.BoolVal()) + + assert.True(t, sm.Delete("other_key")) + assert.True(t, sm.Delete("other_key_string")) + assert.True(t, sm.Delete("other_key_int")) + assert.True(t, sm.Delete("other_key_double")) + assert.True(t, sm.Delete("other_key_bool")) + assert.True(t, sm.Delete("yet_another_key")) + assert.True(t, sm.Delete("yet_another_key_string")) + assert.True(t, sm.Delete("yet_another_key_int")) + assert.True(t, sm.Delete("yet_another_key_double")) + assert.True(t, sm.Delete("yet_another_key_bool")) + assert.False(t, sm.Delete("other_key")) + assert.False(t, sm.Delete("yet_another_key")) + + // Test that the initial key is still there. + val, exist = sm.Get("test_key") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "test_value", val.StringVal()) + + val, exist = sm.Get("test_key2") + assert.True(t, exist) + assert.EqualValues(t, AttributeValueNULL, val.Type()) + assert.EqualValues(t, "", val.StringVal()) + + _, exist = sm.Get("test_key3") + assert.False(t, exist) + + // Test Sort + assert.EqualValues(t, AttributeMap{orig: &origWithNil}, sm.Sort()) +} + +func TestAttributeMapIterationNil(t *testing.T) { + NewAttributeMap().ForEach(func(k string, v AttributeValue) { + // Fail if any element is returned + t.Fail() + }) +} + +func TestAttributeMap_ForEach(t *testing.T) { + rawMap := map[string]AttributeValue{ + "k_string": NewAttributeValueString("123"), + "k_int": NewAttributeValueInt(123), + "k_double": NewAttributeValueDouble(1.23), + "k_bool": NewAttributeValueBool(true), + "k_null": NewAttributeValueNull(), + } + am := NewAttributeMap().InitFromMap(rawMap) + assert.EqualValues(t, 5, am.Len()) + + am.ForEach(func(k string, v AttributeValue) { + assert.True(t, v.Equal(rawMap[k])) + delete(rawMap, k) + }) + assert.EqualValues(t, 0, len(rawMap)) +} + +func TestAttributeMap_InitFromMap(t *testing.T) { + am := NewAttributeMap().InitFromMap(map[string]AttributeValue(nil)) + assert.EqualValues(t, NewAttributeMap(), am) + + rawMap := map[string]AttributeValue{ + "k_string": NewAttributeValueString("123"), + "k_int": NewAttributeValueInt(123), + "k_double": NewAttributeValueDouble(1.23), + "k_bool": NewAttributeValueBool(true), + "k_null": NewAttributeValueNull(), + } + rawOrig := []otlpcommon.KeyValue{ + newAttributeKeyValueString("k_string", "123"), + newAttributeKeyValueInt("k_int", 123), + newAttributeKeyValueDouble("k_double", 1.23), + newAttributeKeyValueBool("k_bool", true), + newAttributeKeyValueNull("k_null"), + } + am = NewAttributeMap().InitFromMap(rawMap) + assert.EqualValues(t, AttributeMap{orig: &rawOrig}.Sort(), am.Sort()) +} + +func TestAttributeValue_CopyTo(t *testing.T) { + // Test nil KvlistValue case for MapVal() func. + dest := NewAttributeValueNull() + orig := &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: nil}} + AttributeValue{orig: orig}.CopyTo(dest) + assert.Nil(t, dest.orig.Value.(*otlpcommon.AnyValue_KvlistValue).KvlistValue) + + // Test nil ArrayValue case for ArrayVal() func. + dest = NewAttributeValueNull() + orig = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: nil}} + AttributeValue{orig: orig}.CopyTo(dest) + assert.Nil(t, dest.orig.Value.(*otlpcommon.AnyValue_ArrayValue).ArrayValue) + + // Test copy empty value. + AttributeValue{orig: &otlpcommon.AnyValue{}}.CopyTo(dest) + assert.Nil(t, dest.orig.Value) +} + +func TestAttributeMap_CopyTo(t *testing.T) { + dest := NewAttributeMap() + // Test CopyTo to empty + NewAttributeMap().CopyTo(dest) + assert.EqualValues(t, 0, dest.Len()) + + // Test CopyTo larger slice + generateTestAttributeMap().CopyTo(dest) + assert.EqualValues(t, generateTestAttributeMap(), dest) + + // Test CopyTo same size slice + generateTestAttributeMap().CopyTo(dest) + assert.EqualValues(t, generateTestAttributeMap(), dest) + + // Test CopyTo with an empty Value in the destination + (*dest.orig)[0].Value = otlpcommon.AnyValue{} + generateTestAttributeMap().CopyTo(dest) + assert.EqualValues(t, generateTestAttributeMap(), dest) +} + +func TestAttributeValue_copyTo(t *testing.T) { + av := NewAttributeValueNull() + destVal := otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_IntValue{}} + av.copyTo(&destVal) + assert.EqualValues(t, nil, destVal.Value) +} + +func TestAttributeMap_Update(t *testing.T) { + origWithNil := []otlpcommon.KeyValue{ + { + Key: "test_key", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "test_value"}}, + }, + { + Key: "test_key2", + Value: otlpcommon.AnyValue{Value: nil}, + }, + } + sm := AttributeMap{ + orig: &origWithNil, + } + + av, exists := sm.Get("test_key") + assert.True(t, exists) + assert.EqualValues(t, AttributeValueSTRING, av.Type()) + assert.EqualValues(t, "test_value", av.StringVal()) + av.SetIntVal(123) + + av2, exists := sm.Get("test_key") + assert.True(t, exists) + assert.EqualValues(t, AttributeValueINT, av2.Type()) + assert.EqualValues(t, 123, av2.IntVal()) + + av, exists = sm.Get("test_key2") + assert.True(t, exists) + assert.EqualValues(t, AttributeValueNULL, av.Type()) + assert.EqualValues(t, "", av.StringVal()) + av.SetIntVal(123) + + av2, exists = sm.Get("test_key2") + assert.True(t, exists) + assert.EqualValues(t, AttributeValueINT, av2.Type()) + assert.EqualValues(t, 123, av2.IntVal()) +} + +func TestAttributeMap_InitEmptyWithCapacity(t *testing.T) { + am := NewAttributeMap() + am.InitEmptyWithCapacity(0) + assert.Equal(t, NewAttributeMap(), am) + assert.Equal(t, 0, am.Len()) +} + +func TestNilStringMap(t *testing.T) { + assert.EqualValues(t, 0, NewStringMap().Len()) + + val, exist := NewStringMap().Get("test_key") + assert.False(t, exist) + assert.EqualValues(t, "", val) + + insertMap := NewStringMap() + insertMap.Insert("k", "v") + assert.EqualValues(t, generateTestStringMap(), insertMap) + + updateMap := NewStringMap() + updateMap.Update("k", "v") + assert.EqualValues(t, NewStringMap(), updateMap) + + upsertMap := NewStringMap() + upsertMap.Upsert("k", "v") + assert.EqualValues(t, generateTestStringMap(), upsertMap) + + deleteMap := NewStringMap() + assert.False(t, deleteMap.Delete("k")) + assert.EqualValues(t, NewStringMap(), deleteMap) + + // Test Sort + assert.EqualValues(t, NewStringMap(), NewStringMap().Sort()) +} + +func TestStringMapWithEmpty(t *testing.T) { + origWithNil := []otlpcommon.StringKeyValue{ + {}, + { + Key: "test_key", + Value: "test_value", + }, + } + sm := StringMap{ + orig: &origWithNil, + } + val, exist := sm.Get("test_key") + assert.True(t, exist) + assert.EqualValues(t, "test_value", val) + + sm.Insert("other_key", "other_value") + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, "other_value", val) + + sm.Update("other_key", "yet_another_value") + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, "yet_another_value", val) + + sm.Upsert("other_key", "other_value") + val, exist = sm.Get("other_key") + assert.True(t, exist) + assert.EqualValues(t, "other_value", val) + + sm.Upsert("yet_another_key", "yet_another_value") + val, exist = sm.Get("yet_another_key") + assert.True(t, exist) + assert.EqualValues(t, "yet_another_value", val) + + assert.True(t, sm.Delete("other_key")) + assert.True(t, sm.Delete("yet_another_key")) + assert.False(t, sm.Delete("other_key")) + assert.False(t, sm.Delete("yet_another_key")) + + // Test that the initial key is still there. + val, exist = sm.Get("test_key") + assert.True(t, exist) + assert.EqualValues(t, "test_value", val) + + // Test Sort + assert.EqualValues(t, StringMap{orig: &origWithNil}, sm.Sort()) +} + +func TestStringMap(t *testing.T) { + origRawMap := map[string]string{"k0": "v0", "k1": "v1", "k2": "v2"} + origMap := NewStringMap().InitFromMap(origRawMap) + sm := NewStringMap().InitFromMap(origRawMap) + assert.EqualValues(t, 3, sm.Len()) + + val, exist := sm.Get("k2") + assert.True(t, exist) + assert.EqualValues(t, "v2", val) + + val, exist = sm.Get("k3") + assert.False(t, exist) + assert.EqualValues(t, "", val) + + sm.Insert("k1", "v1") + assert.EqualValues(t, origMap.Sort(), sm.Sort()) + sm.Insert("k3", "v3") + assert.EqualValues(t, 4, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k0": "v0", "k1": "v1", "k2": "v2", "k3": "v3"}).Sort(), sm.Sort()) + assert.True(t, sm.Delete("k3")) + assert.EqualValues(t, 3, sm.Len()) + assert.EqualValues(t, origMap.Sort(), sm.Sort()) + + sm.Update("k3", "v3") + assert.EqualValues(t, 3, sm.Len()) + assert.EqualValues(t, origMap.Sort(), sm.Sort()) + sm.Update("k2", "v3") + assert.EqualValues(t, 3, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k0": "v0", "k1": "v1", "k2": "v3"}).Sort(), sm.Sort()) + sm.Update("k2", "v2") + assert.EqualValues(t, 3, sm.Len()) + assert.EqualValues(t, origMap.Sort(), sm.Sort()) + + sm.Upsert("k3", "v3") + assert.EqualValues(t, 4, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k0": "v0", "k1": "v1", "k2": "v2", "k3": "v3"}).Sort(), sm.Sort()) + sm.Upsert("k1", "v5") + assert.EqualValues(t, 4, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k0": "v0", "k1": "v5", "k2": "v2", "k3": "v3"}).Sort(), sm.Sort()) + sm.Upsert("k1", "v1") + assert.EqualValues(t, 4, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k0": "v0", "k1": "v1", "k2": "v2", "k3": "v3"}).Sort(), sm.Sort()) + assert.True(t, sm.Delete("k3")) + assert.EqualValues(t, 3, sm.Len()) + assert.EqualValues(t, origMap.Sort(), sm.Sort()) + + assert.False(t, sm.Delete("k3")) + assert.EqualValues(t, 3, sm.Len()) + assert.EqualValues(t, origMap.Sort(), sm.Sort()) + + assert.True(t, sm.Delete("k0")) + assert.EqualValues(t, 2, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k1": "v1", "k2": "v2"}).Sort(), sm.Sort()) + assert.True(t, sm.Delete("k2")) + assert.EqualValues(t, 1, sm.Len()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"k1": "v1"}).Sort(), sm.Sort()) + assert.True(t, sm.Delete("k1")) + assert.EqualValues(t, 0, sm.Len()) +} + +func TestStringMapIterationNil(t *testing.T) { + NewStringMap().ForEach(func(k string, v string) { + // Fail if any element is returned + t.Fail() + }) +} + +func TestStringMap_ForEach(t *testing.T) { + rawMap := map[string]string{"k0": "v0", "k1": "v1", "k2": "v2"} + sm := NewStringMap().InitFromMap(rawMap) + assert.EqualValues(t, 3, sm.Len()) + + sm.ForEach(func(k string, v string) { + assert.EqualValues(t, rawMap[k], v) + delete(rawMap, k) + }) + assert.EqualValues(t, 0, len(rawMap)) +} + +func TestStringMap_CopyTo(t *testing.T) { + dest := NewStringMap() + // Test CopyTo to empty + NewStringMap().CopyTo(dest) + assert.EqualValues(t, 0, dest.Len()) + + // Test CopyTo larger slice + generateTestStringMap().CopyTo(dest) + assert.EqualValues(t, generateTestStringMap(), dest) + + // Test CopyTo same size slice + generateTestStringMap().CopyTo(dest) + assert.EqualValues(t, generateTestStringMap(), dest) +} + +func TestStringMap_InitEmptyWithCapacity(t *testing.T) { + sm := NewStringMap() + sm.InitEmptyWithCapacity(0) + assert.Equal(t, NewStringMap(), sm) + assert.Equal(t, 0, sm.Len()) +} + +func TestStringMap_InitFromMap(t *testing.T) { + sm := NewStringMap().InitFromMap(map[string]string(nil)) + assert.EqualValues(t, NewStringMap(), sm) + + rawMap := map[string]string{"k0": "v0", "k1": "v1", "k2": "v2"} + rawOrig := []otlpcommon.StringKeyValue{ + { + Key: "k0", + Value: "v0", + }, + { + Key: "k1", + Value: "v1", + }, + { + Key: "k2", + Value: "v2", + }, + } + sm = NewStringMap().InitFromMap(rawMap) + assert.EqualValues(t, StringMap{orig: &rawOrig}.Sort(), sm.Sort()) +} + +func BenchmarkAttributeValue_CopyTo(b *testing.B) { + av := NewAttributeValueString("k") + c := NewAttributeValueInt(123) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + c.copyTo(av.orig) + } + if av.IntVal() != 123 { + b.Fail() + } +} + +func BenchmarkAttributeValue_SetIntVal(b *testing.B) { + av := NewAttributeValueString("k") + + b.ResetTimer() + for n := 0; n < b.N; n++ { + av.SetIntVal(int64(n)) + } + if av.IntVal() != int64(b.N-1) { + b.Fail() + } +} + +func BenchmarkAttributeMap_ForEach(b *testing.B) { + const numElements = 20 + rawOrig := make([]otlpcommon.KeyValue, numElements) + for i := 0; i < numElements; i++ { + rawOrig[i] = otlpcommon.KeyValue{ + Key: "k" + strconv.Itoa(i), + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "v" + strconv.Itoa(i)}}, + } + } + am := AttributeMap{ + orig: &rawOrig, + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + numEls := 0 + am.ForEach(func(k string, v AttributeValue) { + numEls++ + }) + if numEls != numElements { + b.Fail() + } + } +} + +func BenchmarkAttributeMap_RangeOverMap(b *testing.B) { + const numElements = 20 + rawOrig := make(map[string]AttributeValue, numElements) + for i := 0; i < numElements; i++ { + key := "k" + strconv.Itoa(i) + rawOrig[key] = NewAttributeValueString("v" + strconv.Itoa(i)) + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + numEls := 0 + for _, v := range rawOrig { + if v.orig == nil { + continue + } + numEls++ + } + if numEls != numElements { + b.Fail() + } + } +} + +func BenchmarkStringMap_ForEach(b *testing.B) { + const numElements = 20 + rawOrig := make([]otlpcommon.StringKeyValue, numElements) + for i := 0; i < numElements; i++ { + rawOrig[i] = otlpcommon.StringKeyValue{ + Key: "k" + strconv.Itoa(i), + Value: "v" + strconv.Itoa(i), + } + } + sm := StringMap{ + orig: &rawOrig, + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + numEls := 0 + sm.ForEach(func(s string, value string) { + numEls++ + }) + if numEls != numElements { + b.Fail() + } + } +} + +func BenchmarkStringMap_RangeOverMap(b *testing.B) { + const numElements = 20 + rawOrig := make(map[string]string, numElements) + for i := 0; i < numElements; i++ { + key := "k" + strconv.Itoa(i) + rawOrig[key] = "v" + strconv.Itoa(i) + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + numEls := 0 + for _, v := range rawOrig { + if v == "" { + continue + } + numEls++ + } + if numEls != numElements { + b.Fail() + } + } +} + +func fillTestAttributeValue(dest AttributeValue) { + dest.SetStringVal("v") +} + +func generateTestAttributeValue() AttributeValue { + av := NewAttributeValueNull() + fillTestAttributeValue(av) + return av +} + +func generateTestStringMap() StringMap { + sm := NewStringMap() + fillTestStringMap(sm) + return sm +} + +func fillTestStringMap(dest StringMap) { + dest.InitFromMap(map[string]string{ + "k": "v", + }) +} + +func generateTestAttributeMap() AttributeMap { + am := NewAttributeMap() + fillTestAttributeMap(am) + return am +} + +func fillTestAttributeMap(dest AttributeMap) { + dest.InitFromMap(map[string]AttributeValue{ + "k": NewAttributeValueString("v"), + }) +} + +func generateTestNullAttributeMap() AttributeMap { + am := NewAttributeMap() + am.InitFromMap(map[string]AttributeValue{ + "k": NewAttributeValueNull(), + }) + return am +} +func generateTestIntAttributeMap() AttributeMap { + am := NewAttributeMap() + am.InitFromMap(map[string]AttributeValue{ + "k": NewAttributeValueInt(123), + }) + return am +} + +func generateTestDoubleAttributeMap() AttributeMap { + am := NewAttributeMap() + am.InitFromMap(map[string]AttributeValue{ + "k": NewAttributeValueDouble(12.3), + }) + return am +} + +func generateTestBoolAttributeMap() AttributeMap { + am := NewAttributeMap() + am.InitFromMap(map[string]AttributeValue{ + "k": NewAttributeValueBool(true), + }) + return am +} + +func fromArray(v []interface{}) AttributeValue { + av := NewAttributeValueArray() + arr := av.ArrayVal() + for _, v := range v { + arr.Append(fromVal(v)) + } + return av +} + +func fromJSONArray(jsonStr string) AttributeValue { + var src []interface{} + err := json.Unmarshal([]byte(jsonStr), &src) + if err != nil { + panic("Invalid input jsonStr:" + jsonStr) + } + return fromArray(src) +} + +func assertArrayJSON(t *testing.T, expectedJSON string, actualArray AttributeValue) { + assert.EqualValues(t, fromJSONArray(expectedJSON).ArrayVal(), actualArray.ArrayVal()) +} + +func TestAttributeValueArray(t *testing.T) { + a1 := NewAttributeValueArray() + assert.EqualValues(t, fromJSONArray(`[]`), a1) + assert.EqualValues(t, AttributeValueARRAY, a1.Type()) + assert.EqualValues(t, NewAnyValueArray(), a1.ArrayVal()) + assert.EqualValues(t, 0, a1.ArrayVal().Len()) + + a1.ArrayVal().Resize(1) + v := a1.ArrayVal().At(0) + v.SetDoubleVal(123) + assertArrayJSON(t, `[123]`, a1) + assert.EqualValues(t, 1, a1.ArrayVal().Len()) + assert.EqualValues(t, AttributeValueDOUBLE, v.Type()) + assert.EqualValues(t, 123, v.DoubleVal()) + + // Create a second array. + a2 := NewAttributeValueArray() + assertArrayJSON(t, `[]`, a2) + assert.EqualValues(t, 0, a2.ArrayVal().Len()) + + a2.ArrayVal().Resize(1) + a2.ArrayVal().At(0).SetStringVal("somestr") + assertArrayJSON(t, `["somestr"]`, a2) + assert.EqualValues(t, 1, a2.ArrayVal().Len()) + + // Insert the second array as a child. + a1.ArrayVal().Append(a2) + assertArrayJSON(t, `[123, ["somestr"]]`, a1) + assert.EqualValues(t, 2, a1.ArrayVal().Len()) + + // Check that the array was correctly inserted. + childArray := a1.ArrayVal().At(1) + assert.EqualValues(t, AttributeValueARRAY, childArray.Type()) + assert.EqualValues(t, 1, childArray.ArrayVal().Len()) + + v = childArray.ArrayVal().At(0) + assert.EqualValues(t, AttributeValueSTRING, v.Type()) + assert.EqualValues(t, "somestr", v.StringVal()) + + // Test nil values case for ArrayVal() func. + a1 = AttributeValue{orig: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: nil}}} + assert.EqualValues(t, NewAnyValueArray(), a1.ArrayVal()) +} + +func TestAnyValueArrayWithNilValues(t *testing.T) { + origWithNil := []otlpcommon.AnyValue{ + {}, + {Value: &otlpcommon.AnyValue_StringValue{StringValue: "test_value"}}, + } + sm := AnyValueArray{ + orig: &origWithNil, + } + + val := sm.At(0) + assert.EqualValues(t, AttributeValueNULL, val.Type()) + assert.EqualValues(t, "", val.StringVal()) + + val = sm.At(1) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "test_value", val.StringVal()) + + sm.Append(NewAttributeValueString("other_value")) + val = sm.At(2) + assert.EqualValues(t, AttributeValueSTRING, val.Type()) + assert.EqualValues(t, "other_value", val.StringVal()) +} diff --git a/internal/otel_collector/consumer/pdata/doc.go b/internal/otel_collector/consumer/pdata/doc.go new file mode 100644 index 00000000000..4b4ca0b041b --- /dev/null +++ b/internal/otel_collector/consumer/pdata/doc.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pdata (pipeline data) implements data structures that represent telemetry data in-memory. +// All data received is converted into this format and travels through the pipeline +// in this format and that is converted from this format by exporters when sending. +// +// Current implementation primarily uses OTLP ProtoBuf structs as the underlying data +// structures for many of of the declared structs. We keep a pointer to OTLP protobuf +// in the "orig" member field. This allows efficient translation to/from OTLP wire +// protocol. Note that the underlying data structure is kept private so that in the +// future we are free to make changes to it to make more optimal. +// +// Most of internal data structures must be created via New* functions. Zero-initialized +// structures in most cases are not valid (read comments for each struct to know if it +// is the case). This is a slight deviation from idiomatic Go to avoid unnecessary +// pointer checks in dozens of functions which assume the invariant that "orig" member +// is non-nil. Several structures also provide New*Slice functions that allows to create +// more than one instance of the struct more efficiently instead of calling New* +// repeatedly. Use it where appropriate. +package pdata diff --git a/internal/otel_collector/consumer/pdata/generated_common.go b/internal/otel_collector/consumer/pdata/generated_common.go new file mode 100644 index 00000000000..9c5e7f73dd5 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_common.go @@ -0,0 +1,187 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" +) + +// InstrumentationLibrary is a message representing the instrumentation library information. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibrary function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibrary struct { + orig *otlpcommon.InstrumentationLibrary +} + +func newInstrumentationLibrary(orig *otlpcommon.InstrumentationLibrary) InstrumentationLibrary { + return InstrumentationLibrary{orig: orig} +} + +// NewInstrumentationLibrary creates a new empty InstrumentationLibrary. +// +// This must be used only in testing code since no "Set" method available. +func NewInstrumentationLibrary() InstrumentationLibrary { + return newInstrumentationLibrary(&otlpcommon.InstrumentationLibrary{}) +} + +// Name returns the name associated with this InstrumentationLibrary. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibrary) Name() string { + return (*ms.orig).Name +} + +// SetName replaces the name associated with this InstrumentationLibrary. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibrary) SetName(v string) { + (*ms.orig).Name = v +} + +// Version returns the version associated with this InstrumentationLibrary. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibrary) Version() string { + return (*ms.orig).Version +} + +// SetVersion replaces the version associated with this InstrumentationLibrary. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibrary) SetVersion(v string) { + (*ms.orig).Version = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms InstrumentationLibrary) CopyTo(dest InstrumentationLibrary) { + dest.SetName(ms.Name()) + dest.SetVersion(ms.Version()) +} + +// AnyValueArray logically represents a slice of AttributeValue. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewAnyValueArray function to create new instances. +// Important: zero-initialized instance is not valid for use. +type AnyValueArray struct { + // orig points to the slice otlpcommon.AnyValue field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]otlpcommon.AnyValue +} + +func newAnyValueArray(orig *[]otlpcommon.AnyValue) AnyValueArray { + return AnyValueArray{orig} +} + +// NewAnyValueArray creates a AnyValueArray with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewAnyValueArray() AnyValueArray { + orig := []otlpcommon.AnyValue(nil) + return AnyValueArray{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewAnyValueArray()". +func (es AnyValueArray) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es AnyValueArray) At(ix int) AttributeValue { + return newAttributeValue(&(*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es AnyValueArray) MoveAndAppendTo(dest AnyValueArray) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es AnyValueArray) CopyTo(dest AnyValueArray) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + } else { + (*dest.orig) = make([]otlpcommon.AnyValue, srcLen) + } + + for i := range *es.orig { + newAttributeValue(&(*es.orig)[i]).CopyTo(newAttributeValue(&(*dest.orig)[i])) + } +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new AnyValueArray can be initialized: +// es := NewAnyValueArray() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es AnyValueArray) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]otlpcommon.AnyValue, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + empty := otlpcommon.AnyValue{} + for i := oldLen; i < newLen; i++ { + *es.orig = append(*es.orig, empty) + } +} + +// Append will increase the length of the AnyValueArray by one and set the +// given AttributeValue at that new position. The original AttributeValue +// could still be referenced so do not reuse it after passing it to this +// method. +func (es AnyValueArray) Append(e AttributeValue) { + *es.orig = append(*es.orig, *e.orig) +} diff --git a/internal/otel_collector/consumer/pdata/generated_common_test.go b/internal/otel_collector/consumer/pdata/generated_common_test.go new file mode 100644 index 00000000000..a7765894034 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_common_test.go @@ -0,0 +1,185 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" +) + +func TestInstrumentationLibrary_CopyTo(t *testing.T) { + ms := NewInstrumentationLibrary() + generateTestInstrumentationLibrary().CopyTo(ms) + assert.EqualValues(t, generateTestInstrumentationLibrary(), ms) +} + +func TestInstrumentationLibrary_Name(t *testing.T) { + ms := NewInstrumentationLibrary() + assert.EqualValues(t, "", ms.Name()) + testValName := "test_name" + ms.SetName(testValName) + assert.EqualValues(t, testValName, ms.Name()) +} + +func TestInstrumentationLibrary_Version(t *testing.T) { + ms := NewInstrumentationLibrary() + assert.EqualValues(t, "", ms.Version()) + testValVersion := "test_version" + ms.SetVersion(testValVersion) + assert.EqualValues(t, testValVersion, ms.Version()) +} + +func TestAnyValueArray(t *testing.T) { + es := NewAnyValueArray() + assert.EqualValues(t, 0, es.Len()) + es = newAnyValueArray(&[]otlpcommon.AnyValue{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewAttributeValue() + testVal := generateTestAttributeValue() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestAttributeValue(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestAnyValueArray_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestAnyValueArray() + dest := NewAnyValueArray() + src := generateTestAnyValueArray() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestAnyValueArray(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestAnyValueArray(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestAnyValueArray().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestAnyValueArray_CopyTo(t *testing.T) { + dest := NewAnyValueArray() + // Test CopyTo to empty + NewAnyValueArray().CopyTo(dest) + assert.EqualValues(t, NewAnyValueArray(), dest) + + // Test CopyTo larger slice + generateTestAnyValueArray().CopyTo(dest) + assert.EqualValues(t, generateTestAnyValueArray(), dest) + + // Test CopyTo same size slice + generateTestAnyValueArray().CopyTo(dest) + assert.EqualValues(t, generateTestAnyValueArray(), dest) +} + +func TestAnyValueArray_Resize(t *testing.T) { + es := generateTestAnyValueArray() + emptyVal := NewAttributeValue() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpcommon.AnyValue]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpcommon.AnyValue]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpcommon.AnyValue]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpcommon.AnyValue]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestAnyValueArray_Append(t *testing.T) { + es := generateTestAnyValueArray() + + emptyVal := NewAttributeValue() + es.Append(emptyVal) + assert.EqualValues(t, *(es.At(7)).orig, *emptyVal.orig) + + emptyVal2 := NewAttributeValue() + es.Append(emptyVal2) + assert.EqualValues(t, *(es.At(8)).orig, *emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func generateTestInstrumentationLibrary() InstrumentationLibrary { + tv := NewInstrumentationLibrary() + fillTestInstrumentationLibrary(tv) + return tv +} + +func fillTestInstrumentationLibrary(tv InstrumentationLibrary) { + tv.SetName("test_name") + tv.SetVersion("test_version") +} + +func generateTestAnyValueArray() AnyValueArray { + tv := NewAnyValueArray() + fillTestAnyValueArray(tv) + return tv +} + +func fillTestAnyValueArray(tv AnyValueArray) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestAttributeValue(tv.At(i)) + } +} diff --git a/internal/otel_collector/consumer/pdata/generated_log.go b/internal/otel_collector/consumer/pdata/generated_log.go new file mode 100644 index 00000000000..82110449f8d --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_log.go @@ -0,0 +1,612 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "go.opentelemetry.io/collector/internal/data" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +// ResourceLogsSlice logically represents a slice of ResourceLogs. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResourceLogsSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ResourceLogsSlice struct { + // orig points to the slice otlplogs.ResourceLogs field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlplogs.ResourceLogs +} + +func newResourceLogsSlice(orig *[]*otlplogs.ResourceLogs) ResourceLogsSlice { + return ResourceLogsSlice{orig} +} + +// NewResourceLogsSlice creates a ResourceLogsSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewResourceLogsSlice() ResourceLogsSlice { + orig := []*otlplogs.ResourceLogs(nil) + return ResourceLogsSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewResourceLogsSlice()". +func (es ResourceLogsSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es ResourceLogsSlice) At(ix int) ResourceLogs { + return newResourceLogs((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es ResourceLogsSlice) MoveAndAppendTo(dest ResourceLogsSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es ResourceLogsSlice) CopyTo(dest ResourceLogsSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newResourceLogs((*es.orig)[i]).CopyTo(newResourceLogs((*dest.orig)[i])) + } + return + } + origs := make([]otlplogs.ResourceLogs, srcLen) + wrappers := make([]*otlplogs.ResourceLogs, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newResourceLogs((*es.orig)[i]).CopyTo(newResourceLogs(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new ResourceLogsSlice can be initialized: +// es := NewResourceLogsSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es ResourceLogsSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlplogs.ResourceLogs, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlplogs.ResourceLogs, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the ResourceLogsSlice by one and set the +// given ResourceLogs at that new position. The original ResourceLogs +// could still be referenced so do not reuse it after passing it to this +// method. +func (es ResourceLogsSlice) Append(e ResourceLogs) { + *es.orig = append(*es.orig, e.orig) +} + +// ResourceLogs is a collection of logs from a Resource. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResourceLogs function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ResourceLogs struct { + orig *otlplogs.ResourceLogs +} + +func newResourceLogs(orig *otlplogs.ResourceLogs) ResourceLogs { + return ResourceLogs{orig: orig} +} + +// NewResourceLogs creates a new empty ResourceLogs. +// +// This must be used only in testing code since no "Set" method available. +func NewResourceLogs() ResourceLogs { + return newResourceLogs(&otlplogs.ResourceLogs{}) +} + +// Resource returns the resource associated with this ResourceLogs. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ResourceLogs) Resource() Resource { + return newResource(&(*ms.orig).Resource) +} + +// InstrumentationLibraryLogs returns the InstrumentationLibraryLogs associated with this ResourceLogs. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ResourceLogs) InstrumentationLibraryLogs() InstrumentationLibraryLogsSlice { + return newInstrumentationLibraryLogsSlice(&(*ms.orig).InstrumentationLibraryLogs) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms ResourceLogs) CopyTo(dest ResourceLogs) { + ms.Resource().CopyTo(dest.Resource()) + ms.InstrumentationLibraryLogs().CopyTo(dest.InstrumentationLibraryLogs()) +} + +// InstrumentationLibraryLogsSlice logically represents a slice of InstrumentationLibraryLogs. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibraryLogsSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibraryLogsSlice struct { + // orig points to the slice otlplogs.InstrumentationLibraryLogs field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlplogs.InstrumentationLibraryLogs +} + +func newInstrumentationLibraryLogsSlice(orig *[]*otlplogs.InstrumentationLibraryLogs) InstrumentationLibraryLogsSlice { + return InstrumentationLibraryLogsSlice{orig} +} + +// NewInstrumentationLibraryLogsSlice creates a InstrumentationLibraryLogsSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewInstrumentationLibraryLogsSlice() InstrumentationLibraryLogsSlice { + orig := []*otlplogs.InstrumentationLibraryLogs(nil) + return InstrumentationLibraryLogsSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewInstrumentationLibraryLogsSlice()". +func (es InstrumentationLibraryLogsSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es InstrumentationLibraryLogsSlice) At(ix int) InstrumentationLibraryLogs { + return newInstrumentationLibraryLogs((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es InstrumentationLibraryLogsSlice) MoveAndAppendTo(dest InstrumentationLibraryLogsSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es InstrumentationLibraryLogsSlice) CopyTo(dest InstrumentationLibraryLogsSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newInstrumentationLibraryLogs((*es.orig)[i]).CopyTo(newInstrumentationLibraryLogs((*dest.orig)[i])) + } + return + } + origs := make([]otlplogs.InstrumentationLibraryLogs, srcLen) + wrappers := make([]*otlplogs.InstrumentationLibraryLogs, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newInstrumentationLibraryLogs((*es.orig)[i]).CopyTo(newInstrumentationLibraryLogs(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new InstrumentationLibraryLogsSlice can be initialized: +// es := NewInstrumentationLibraryLogsSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es InstrumentationLibraryLogsSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlplogs.InstrumentationLibraryLogs, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlplogs.InstrumentationLibraryLogs, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the InstrumentationLibraryLogsSlice by one and set the +// given InstrumentationLibraryLogs at that new position. The original InstrumentationLibraryLogs +// could still be referenced so do not reuse it after passing it to this +// method. +func (es InstrumentationLibraryLogsSlice) Append(e InstrumentationLibraryLogs) { + *es.orig = append(*es.orig, e.orig) +} + +// InstrumentationLibraryLogs is a collection of logs from a LibraryInstrumentation. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibraryLogs function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibraryLogs struct { + orig *otlplogs.InstrumentationLibraryLogs +} + +func newInstrumentationLibraryLogs(orig *otlplogs.InstrumentationLibraryLogs) InstrumentationLibraryLogs { + return InstrumentationLibraryLogs{orig: orig} +} + +// NewInstrumentationLibraryLogs creates a new empty InstrumentationLibraryLogs. +// +// This must be used only in testing code since no "Set" method available. +func NewInstrumentationLibraryLogs() InstrumentationLibraryLogs { + return newInstrumentationLibraryLogs(&otlplogs.InstrumentationLibraryLogs{}) +} + +// InstrumentationLibrary returns the instrumentationlibrary associated with this InstrumentationLibraryLogs. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibraryLogs) InstrumentationLibrary() InstrumentationLibrary { + return newInstrumentationLibrary(&(*ms.orig).InstrumentationLibrary) +} + +// Logs returns the Logs associated with this InstrumentationLibraryLogs. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibraryLogs) Logs() LogSlice { + return newLogSlice(&(*ms.orig).Logs) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms InstrumentationLibraryLogs) CopyTo(dest InstrumentationLibraryLogs) { + ms.InstrumentationLibrary().CopyTo(dest.InstrumentationLibrary()) + ms.Logs().CopyTo(dest.Logs()) +} + +// LogSlice logically represents a slice of LogRecord. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewLogSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type LogSlice struct { + // orig points to the slice otlplogs.LogRecord field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlplogs.LogRecord +} + +func newLogSlice(orig *[]*otlplogs.LogRecord) LogSlice { + return LogSlice{orig} +} + +// NewLogSlice creates a LogSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewLogSlice() LogSlice { + orig := []*otlplogs.LogRecord(nil) + return LogSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewLogSlice()". +func (es LogSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es LogSlice) At(ix int) LogRecord { + return newLogRecord((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es LogSlice) MoveAndAppendTo(dest LogSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es LogSlice) CopyTo(dest LogSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newLogRecord((*es.orig)[i]).CopyTo(newLogRecord((*dest.orig)[i])) + } + return + } + origs := make([]otlplogs.LogRecord, srcLen) + wrappers := make([]*otlplogs.LogRecord, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newLogRecord((*es.orig)[i]).CopyTo(newLogRecord(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new LogSlice can be initialized: +// es := NewLogSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es LogSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlplogs.LogRecord, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlplogs.LogRecord, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the LogSlice by one and set the +// given LogRecord at that new position. The original LogRecord +// could still be referenced so do not reuse it after passing it to this +// method. +func (es LogSlice) Append(e LogRecord) { + *es.orig = append(*es.orig, e.orig) +} + +// LogRecord are experimental implementation of OpenTelemetry Log Data Model. + +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewLogRecord function to create new instances. +// Important: zero-initialized instance is not valid for use. +type LogRecord struct { + orig *otlplogs.LogRecord +} + +func newLogRecord(orig *otlplogs.LogRecord) LogRecord { + return LogRecord{orig: orig} +} + +// NewLogRecord creates a new empty LogRecord. +// +// This must be used only in testing code since no "Set" method available. +func NewLogRecord() LogRecord { + return newLogRecord(&otlplogs.LogRecord{}) +} + +// Timestamp returns the timestamp associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// TraceID returns the traceid associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) TraceID() TraceID { + return TraceID((*ms.orig).TraceId) +} + +// SetTraceID replaces the traceid associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetTraceID(v TraceID) { + (*ms.orig).TraceId = data.TraceID(v) +} + +// SpanID returns the spanid associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SpanID() SpanID { + return SpanID((*ms.orig).SpanId) +} + +// SetSpanID replaces the spanid associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetSpanID(v SpanID) { + (*ms.orig).SpanId = data.SpanID(v) +} + +// Flags returns the flags associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) Flags() uint32 { + return uint32((*ms.orig).Flags) +} + +// SetFlags replaces the flags associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetFlags(v uint32) { + (*ms.orig).Flags = uint32(v) +} + +// SeverityText returns the severitytext associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SeverityText() string { + return (*ms.orig).SeverityText +} + +// SetSeverityText replaces the severitytext associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetSeverityText(v string) { + (*ms.orig).SeverityText = v +} + +// SeverityNumber returns the severitynumber associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SeverityNumber() SeverityNumber { + return SeverityNumber((*ms.orig).SeverityNumber) +} + +// SetSeverityNumber replaces the severitynumber associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetSeverityNumber(v SeverityNumber) { + (*ms.orig).SeverityNumber = otlplogs.SeverityNumber(v) +} + +// Name returns the name associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) Name() string { + return (*ms.orig).Name +} + +// SetName replaces the name associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetName(v string) { + (*ms.orig).Name = v +} + +// Body returns the body associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) Body() AttributeValue { + return newAttributeValue(&(*ms.orig).Body) +} + +// Attributes returns the Attributes associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) Attributes() AttributeMap { + return newAttributeMap(&(*ms.orig).Attributes) +} + +// DroppedAttributesCount returns the droppedattributescount associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) DroppedAttributesCount() uint32 { + return (*ms.orig).DroppedAttributesCount +} + +// SetDroppedAttributesCount replaces the droppedattributescount associated with this LogRecord. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms LogRecord) SetDroppedAttributesCount(v uint32) { + (*ms.orig).DroppedAttributesCount = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms LogRecord) CopyTo(dest LogRecord) { + dest.SetTimestamp(ms.Timestamp()) + dest.SetTraceID(ms.TraceID()) + dest.SetSpanID(ms.SpanID()) + dest.SetFlags(ms.Flags()) + dest.SetSeverityText(ms.SeverityText()) + dest.SetSeverityNumber(ms.SeverityNumber()) + dest.SetName(ms.Name()) + ms.Body().CopyTo(dest.Body()) + ms.Attributes().CopyTo(dest.Attributes()) + dest.SetDroppedAttributesCount(ms.DroppedAttributesCount()) +} diff --git a/internal/otel_collector/consumer/pdata/generated_log_test.go b/internal/otel_collector/consumer/pdata/generated_log_test.go new file mode 100644 index 00000000000..3c25eed6d14 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_log_test.go @@ -0,0 +1,569 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +func TestResourceLogsSlice(t *testing.T) { + es := NewResourceLogsSlice() + assert.EqualValues(t, 0, es.Len()) + es = newResourceLogsSlice(&[]*otlplogs.ResourceLogs{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewResourceLogs() + testVal := generateTestResourceLogs() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestResourceLogs(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestResourceLogsSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestResourceLogsSlice() + dest := NewResourceLogsSlice() + src := generateTestResourceLogsSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestResourceLogsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestResourceLogsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestResourceLogsSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestResourceLogsSlice_CopyTo(t *testing.T) { + dest := NewResourceLogsSlice() + // Test CopyTo to empty + NewResourceLogsSlice().CopyTo(dest) + assert.EqualValues(t, NewResourceLogsSlice(), dest) + + // Test CopyTo larger slice + generateTestResourceLogsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestResourceLogsSlice(), dest) + + // Test CopyTo same size slice + generateTestResourceLogsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestResourceLogsSlice(), dest) +} + +func TestResourceLogsSlice_Resize(t *testing.T) { + es := generateTestResourceLogsSlice() + emptyVal := NewResourceLogs() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlplogs.ResourceLogs]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlplogs.ResourceLogs]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlplogs.ResourceLogs]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlplogs.ResourceLogs]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestResourceLogsSlice_Append(t *testing.T) { + es := generateTestResourceLogsSlice() + + emptyVal := NewResourceLogs() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewResourceLogs() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestResourceLogs_CopyTo(t *testing.T) { + ms := NewResourceLogs() + generateTestResourceLogs().CopyTo(ms) + assert.EqualValues(t, generateTestResourceLogs(), ms) +} + +func TestResourceLogs_Resource(t *testing.T) { + ms := NewResourceLogs() + fillTestResource(ms.Resource()) + assert.EqualValues(t, generateTestResource(), ms.Resource()) +} + +func TestResourceLogs_InstrumentationLibraryLogs(t *testing.T) { + ms := NewResourceLogs() + assert.EqualValues(t, NewInstrumentationLibraryLogsSlice(), ms.InstrumentationLibraryLogs()) + fillTestInstrumentationLibraryLogsSlice(ms.InstrumentationLibraryLogs()) + testValInstrumentationLibraryLogs := generateTestInstrumentationLibraryLogsSlice() + assert.EqualValues(t, testValInstrumentationLibraryLogs, ms.InstrumentationLibraryLogs()) +} + +func TestInstrumentationLibraryLogsSlice(t *testing.T) { + es := NewInstrumentationLibraryLogsSlice() + assert.EqualValues(t, 0, es.Len()) + es = newInstrumentationLibraryLogsSlice(&[]*otlplogs.InstrumentationLibraryLogs{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewInstrumentationLibraryLogs() + testVal := generateTestInstrumentationLibraryLogs() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestInstrumentationLibraryLogs(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestInstrumentationLibraryLogsSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestInstrumentationLibraryLogsSlice() + dest := NewInstrumentationLibraryLogsSlice() + src := generateTestInstrumentationLibraryLogsSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryLogsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryLogsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestInstrumentationLibraryLogsSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestInstrumentationLibraryLogsSlice_CopyTo(t *testing.T) { + dest := NewInstrumentationLibraryLogsSlice() + // Test CopyTo to empty + NewInstrumentationLibraryLogsSlice().CopyTo(dest) + assert.EqualValues(t, NewInstrumentationLibraryLogsSlice(), dest) + + // Test CopyTo larger slice + generateTestInstrumentationLibraryLogsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryLogsSlice(), dest) + + // Test CopyTo same size slice + generateTestInstrumentationLibraryLogsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryLogsSlice(), dest) +} + +func TestInstrumentationLibraryLogsSlice_Resize(t *testing.T) { + es := generateTestInstrumentationLibraryLogsSlice() + emptyVal := NewInstrumentationLibraryLogs() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlplogs.InstrumentationLibraryLogs]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlplogs.InstrumentationLibraryLogs]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlplogs.InstrumentationLibraryLogs]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlplogs.InstrumentationLibraryLogs]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestInstrumentationLibraryLogsSlice_Append(t *testing.T) { + es := generateTestInstrumentationLibraryLogsSlice() + + emptyVal := NewInstrumentationLibraryLogs() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewInstrumentationLibraryLogs() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestInstrumentationLibraryLogs_CopyTo(t *testing.T) { + ms := NewInstrumentationLibraryLogs() + generateTestInstrumentationLibraryLogs().CopyTo(ms) + assert.EqualValues(t, generateTestInstrumentationLibraryLogs(), ms) +} + +func TestInstrumentationLibraryLogs_InstrumentationLibrary(t *testing.T) { + ms := NewInstrumentationLibraryLogs() + fillTestInstrumentationLibrary(ms.InstrumentationLibrary()) + assert.EqualValues(t, generateTestInstrumentationLibrary(), ms.InstrumentationLibrary()) +} + +func TestInstrumentationLibraryLogs_Logs(t *testing.T) { + ms := NewInstrumentationLibraryLogs() + assert.EqualValues(t, NewLogSlice(), ms.Logs()) + fillTestLogSlice(ms.Logs()) + testValLogs := generateTestLogSlice() + assert.EqualValues(t, testValLogs, ms.Logs()) +} + +func TestLogSlice(t *testing.T) { + es := NewLogSlice() + assert.EqualValues(t, 0, es.Len()) + es = newLogSlice(&[]*otlplogs.LogRecord{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewLogRecord() + testVal := generateTestLogRecord() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestLogRecord(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestLogSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestLogSlice() + dest := NewLogSlice() + src := generateTestLogSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestLogSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestLogSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestLogSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestLogSlice_CopyTo(t *testing.T) { + dest := NewLogSlice() + // Test CopyTo to empty + NewLogSlice().CopyTo(dest) + assert.EqualValues(t, NewLogSlice(), dest) + + // Test CopyTo larger slice + generateTestLogSlice().CopyTo(dest) + assert.EqualValues(t, generateTestLogSlice(), dest) + + // Test CopyTo same size slice + generateTestLogSlice().CopyTo(dest) + assert.EqualValues(t, generateTestLogSlice(), dest) +} + +func TestLogSlice_Resize(t *testing.T) { + es := generateTestLogSlice() + emptyVal := NewLogRecord() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlplogs.LogRecord]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlplogs.LogRecord]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlplogs.LogRecord]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlplogs.LogRecord]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestLogSlice_Append(t *testing.T) { + es := generateTestLogSlice() + + emptyVal := NewLogRecord() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewLogRecord() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestLogRecord_CopyTo(t *testing.T) { + ms := NewLogRecord() + generateTestLogRecord().CopyTo(ms) + assert.EqualValues(t, generateTestLogRecord(), ms) +} + +func TestLogRecord_Timestamp(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestLogRecord_TraceID(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, NewTraceID([16]byte{}), ms.TraceID()) + testValTraceID := NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}) + ms.SetTraceID(testValTraceID) + assert.EqualValues(t, testValTraceID, ms.TraceID()) +} + +func TestLogRecord_SpanID(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, NewSpanID([8]byte{}), ms.SpanID()) + testValSpanID := NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + ms.SetSpanID(testValSpanID) + assert.EqualValues(t, testValSpanID, ms.SpanID()) +} + +func TestLogRecord_Flags(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, uint32(0), ms.Flags()) + testValFlags := uint32(0x01) + ms.SetFlags(testValFlags) + assert.EqualValues(t, testValFlags, ms.Flags()) +} + +func TestLogRecord_SeverityText(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, "", ms.SeverityText()) + testValSeverityText := "INFO" + ms.SetSeverityText(testValSeverityText) + assert.EqualValues(t, testValSeverityText, ms.SeverityText()) +} + +func TestLogRecord_SeverityNumber(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, SeverityNumberUNDEFINED, ms.SeverityNumber()) + testValSeverityNumber := SeverityNumberINFO + ms.SetSeverityNumber(testValSeverityNumber) + assert.EqualValues(t, testValSeverityNumber, ms.SeverityNumber()) +} + +func TestLogRecord_Name(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, "", ms.Name()) + testValName := "test_name" + ms.SetName(testValName) + assert.EqualValues(t, testValName, ms.Name()) +} + +func TestLogRecord_Body(t *testing.T) { + ms := NewLogRecord() + fillTestAttributeValue(ms.Body()) + assert.EqualValues(t, generateTestAttributeValue(), ms.Body()) +} + +func TestLogRecord_Attributes(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, NewAttributeMap(), ms.Attributes()) + fillTestAttributeMap(ms.Attributes()) + testValAttributes := generateTestAttributeMap() + assert.EqualValues(t, testValAttributes, ms.Attributes()) +} + +func TestLogRecord_DroppedAttributesCount(t *testing.T) { + ms := NewLogRecord() + assert.EqualValues(t, uint32(0), ms.DroppedAttributesCount()) + testValDroppedAttributesCount := uint32(17) + ms.SetDroppedAttributesCount(testValDroppedAttributesCount) + assert.EqualValues(t, testValDroppedAttributesCount, ms.DroppedAttributesCount()) +} + +func generateTestResourceLogsSlice() ResourceLogsSlice { + tv := NewResourceLogsSlice() + fillTestResourceLogsSlice(tv) + return tv +} + +func fillTestResourceLogsSlice(tv ResourceLogsSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestResourceLogs(tv.At(i)) + } +} + +func generateTestResourceLogs() ResourceLogs { + tv := NewResourceLogs() + fillTestResourceLogs(tv) + return tv +} + +func fillTestResourceLogs(tv ResourceLogs) { + fillTestResource(tv.Resource()) + fillTestInstrumentationLibraryLogsSlice(tv.InstrumentationLibraryLogs()) +} + +func generateTestInstrumentationLibraryLogsSlice() InstrumentationLibraryLogsSlice { + tv := NewInstrumentationLibraryLogsSlice() + fillTestInstrumentationLibraryLogsSlice(tv) + return tv +} + +func fillTestInstrumentationLibraryLogsSlice(tv InstrumentationLibraryLogsSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestInstrumentationLibraryLogs(tv.At(i)) + } +} + +func generateTestInstrumentationLibraryLogs() InstrumentationLibraryLogs { + tv := NewInstrumentationLibraryLogs() + fillTestInstrumentationLibraryLogs(tv) + return tv +} + +func fillTestInstrumentationLibraryLogs(tv InstrumentationLibraryLogs) { + fillTestInstrumentationLibrary(tv.InstrumentationLibrary()) + fillTestLogSlice(tv.Logs()) +} + +func generateTestLogSlice() LogSlice { + tv := NewLogSlice() + fillTestLogSlice(tv) + return tv +} + +func fillTestLogSlice(tv LogSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestLogRecord(tv.At(i)) + } +} + +func generateTestLogRecord() LogRecord { + tv := NewLogRecord() + fillTestLogRecord(tv) + return tv +} + +func fillTestLogRecord(tv LogRecord) { + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetTraceID(NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})) + tv.SetSpanID(NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + tv.SetFlags(uint32(0x01)) + tv.SetSeverityText("INFO") + tv.SetSeverityNumber(SeverityNumberINFO) + tv.SetName("test_name") + fillTestAttributeValue(tv.Body()) + fillTestAttributeMap(tv.Attributes()) + tv.SetDroppedAttributesCount(uint32(17)) +} diff --git a/internal/otel_collector/consumer/pdata/generated_metrics.go b/internal/otel_collector/consumer/pdata/generated_metrics.go new file mode 100644 index 00000000000..5aa528023be --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_metrics.go @@ -0,0 +1,2491 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +// ResourceMetricsSlice logically represents a slice of ResourceMetrics. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResourceMetricsSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ResourceMetricsSlice struct { + // orig points to the slice otlpmetrics.ResourceMetrics field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.ResourceMetrics +} + +func newResourceMetricsSlice(orig *[]*otlpmetrics.ResourceMetrics) ResourceMetricsSlice { + return ResourceMetricsSlice{orig} +} + +// NewResourceMetricsSlice creates a ResourceMetricsSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewResourceMetricsSlice() ResourceMetricsSlice { + orig := []*otlpmetrics.ResourceMetrics(nil) + return ResourceMetricsSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewResourceMetricsSlice()". +func (es ResourceMetricsSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es ResourceMetricsSlice) At(ix int) ResourceMetrics { + return newResourceMetrics((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es ResourceMetricsSlice) MoveAndAppendTo(dest ResourceMetricsSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es ResourceMetricsSlice) CopyTo(dest ResourceMetricsSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newResourceMetrics((*es.orig)[i]).CopyTo(newResourceMetrics((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.ResourceMetrics, srcLen) + wrappers := make([]*otlpmetrics.ResourceMetrics, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newResourceMetrics((*es.orig)[i]).CopyTo(newResourceMetrics(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new ResourceMetricsSlice can be initialized: +// es := NewResourceMetricsSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es ResourceMetricsSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.ResourceMetrics, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.ResourceMetrics, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the ResourceMetricsSlice by one and set the +// given ResourceMetrics at that new position. The original ResourceMetrics +// could still be referenced so do not reuse it after passing it to this +// method. +func (es ResourceMetricsSlice) Append(e ResourceMetrics) { + *es.orig = append(*es.orig, e.orig) +} + +// InstrumentationLibraryMetrics is a collection of metrics from a LibraryInstrumentation. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResourceMetrics function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ResourceMetrics struct { + orig *otlpmetrics.ResourceMetrics +} + +func newResourceMetrics(orig *otlpmetrics.ResourceMetrics) ResourceMetrics { + return ResourceMetrics{orig: orig} +} + +// NewResourceMetrics creates a new empty ResourceMetrics. +// +// This must be used only in testing code since no "Set" method available. +func NewResourceMetrics() ResourceMetrics { + return newResourceMetrics(&otlpmetrics.ResourceMetrics{}) +} + +// Resource returns the resource associated with this ResourceMetrics. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ResourceMetrics) Resource() Resource { + return newResource(&(*ms.orig).Resource) +} + +// InstrumentationLibraryMetrics returns the InstrumentationLibraryMetrics associated with this ResourceMetrics. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ResourceMetrics) InstrumentationLibraryMetrics() InstrumentationLibraryMetricsSlice { + return newInstrumentationLibraryMetricsSlice(&(*ms.orig).InstrumentationLibraryMetrics) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms ResourceMetrics) CopyTo(dest ResourceMetrics) { + ms.Resource().CopyTo(dest.Resource()) + ms.InstrumentationLibraryMetrics().CopyTo(dest.InstrumentationLibraryMetrics()) +} + +// InstrumentationLibraryMetricsSlice logically represents a slice of InstrumentationLibraryMetrics. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibraryMetricsSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibraryMetricsSlice struct { + // orig points to the slice otlpmetrics.InstrumentationLibraryMetrics field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.InstrumentationLibraryMetrics +} + +func newInstrumentationLibraryMetricsSlice(orig *[]*otlpmetrics.InstrumentationLibraryMetrics) InstrumentationLibraryMetricsSlice { + return InstrumentationLibraryMetricsSlice{orig} +} + +// NewInstrumentationLibraryMetricsSlice creates a InstrumentationLibraryMetricsSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewInstrumentationLibraryMetricsSlice() InstrumentationLibraryMetricsSlice { + orig := []*otlpmetrics.InstrumentationLibraryMetrics(nil) + return InstrumentationLibraryMetricsSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewInstrumentationLibraryMetricsSlice()". +func (es InstrumentationLibraryMetricsSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es InstrumentationLibraryMetricsSlice) At(ix int) InstrumentationLibraryMetrics { + return newInstrumentationLibraryMetrics((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es InstrumentationLibraryMetricsSlice) MoveAndAppendTo(dest InstrumentationLibraryMetricsSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es InstrumentationLibraryMetricsSlice) CopyTo(dest InstrumentationLibraryMetricsSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newInstrumentationLibraryMetrics((*es.orig)[i]).CopyTo(newInstrumentationLibraryMetrics((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.InstrumentationLibraryMetrics, srcLen) + wrappers := make([]*otlpmetrics.InstrumentationLibraryMetrics, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newInstrumentationLibraryMetrics((*es.orig)[i]).CopyTo(newInstrumentationLibraryMetrics(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new InstrumentationLibraryMetricsSlice can be initialized: +// es := NewInstrumentationLibraryMetricsSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es InstrumentationLibraryMetricsSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.InstrumentationLibraryMetrics, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.InstrumentationLibraryMetrics, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the InstrumentationLibraryMetricsSlice by one and set the +// given InstrumentationLibraryMetrics at that new position. The original InstrumentationLibraryMetrics +// could still be referenced so do not reuse it after passing it to this +// method. +func (es InstrumentationLibraryMetricsSlice) Append(e InstrumentationLibraryMetrics) { + *es.orig = append(*es.orig, e.orig) +} + +// InstrumentationLibraryMetrics is a collection of metrics from a LibraryInstrumentation. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibraryMetrics function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibraryMetrics struct { + orig *otlpmetrics.InstrumentationLibraryMetrics +} + +func newInstrumentationLibraryMetrics(orig *otlpmetrics.InstrumentationLibraryMetrics) InstrumentationLibraryMetrics { + return InstrumentationLibraryMetrics{orig: orig} +} + +// NewInstrumentationLibraryMetrics creates a new empty InstrumentationLibraryMetrics. +// +// This must be used only in testing code since no "Set" method available. +func NewInstrumentationLibraryMetrics() InstrumentationLibraryMetrics { + return newInstrumentationLibraryMetrics(&otlpmetrics.InstrumentationLibraryMetrics{}) +} + +// InstrumentationLibrary returns the instrumentationlibrary associated with this InstrumentationLibraryMetrics. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibraryMetrics) InstrumentationLibrary() InstrumentationLibrary { + return newInstrumentationLibrary(&(*ms.orig).InstrumentationLibrary) +} + +// Metrics returns the Metrics associated with this InstrumentationLibraryMetrics. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibraryMetrics) Metrics() MetricSlice { + return newMetricSlice(&(*ms.orig).Metrics) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms InstrumentationLibraryMetrics) CopyTo(dest InstrumentationLibraryMetrics) { + ms.InstrumentationLibrary().CopyTo(dest.InstrumentationLibrary()) + ms.Metrics().CopyTo(dest.Metrics()) +} + +// MetricSlice logically represents a slice of Metric. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewMetricSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type MetricSlice struct { + // orig points to the slice otlpmetrics.Metric field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.Metric +} + +func newMetricSlice(orig *[]*otlpmetrics.Metric) MetricSlice { + return MetricSlice{orig} +} + +// NewMetricSlice creates a MetricSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewMetricSlice() MetricSlice { + orig := []*otlpmetrics.Metric(nil) + return MetricSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewMetricSlice()". +func (es MetricSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es MetricSlice) At(ix int) Metric { + return newMetric((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es MetricSlice) MoveAndAppendTo(dest MetricSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es MetricSlice) CopyTo(dest MetricSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newMetric((*es.orig)[i]).CopyTo(newMetric((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.Metric, srcLen) + wrappers := make([]*otlpmetrics.Metric, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newMetric((*es.orig)[i]).CopyTo(newMetric(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new MetricSlice can be initialized: +// es := NewMetricSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es MetricSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.Metric, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.Metric, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the MetricSlice by one and set the +// given Metric at that new position. The original Metric +// could still be referenced so do not reuse it after passing it to this +// method. +func (es MetricSlice) Append(e Metric) { + *es.orig = append(*es.orig, e.orig) +} + +// Metric represents one metric as a collection of datapoints. +// See Metric definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/metrics/v1/metrics.proto +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewMetric function to create new instances. +// Important: zero-initialized instance is not valid for use. +type Metric struct { + orig *otlpmetrics.Metric +} + +func newMetric(orig *otlpmetrics.Metric) Metric { + return Metric{orig: orig} +} + +// NewMetric creates a new empty Metric. +// +// This must be used only in testing code since no "Set" method available. +func NewMetric() Metric { + return newMetric(&otlpmetrics.Metric{}) +} + +// Name returns the name associated with this Metric. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Metric) Name() string { + return (*ms.orig).Name +} + +// SetName replaces the name associated with this Metric. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Metric) SetName(v string) { + (*ms.orig).Name = v +} + +// Description returns the description associated with this Metric. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Metric) Description() string { + return (*ms.orig).Description +} + +// SetDescription replaces the description associated with this Metric. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Metric) SetDescription(v string) { + (*ms.orig).Description = v +} + +// Unit returns the unit associated with this Metric. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Metric) Unit() string { + return (*ms.orig).Unit +} + +// SetUnit replaces the unit associated with this Metric. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Metric) SetUnit(v string) { + (*ms.orig).Unit = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms Metric) CopyTo(dest Metric) { + dest.SetName(ms.Name()) + dest.SetDescription(ms.Description()) + dest.SetUnit(ms.Unit()) + copyData(ms.orig, dest.orig) +} + +// IntGauge represents the type of a int scalar metric that always exports the "current value" for every data point. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntGauge function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntGauge struct { + orig *otlpmetrics.IntGauge +} + +func newIntGauge(orig *otlpmetrics.IntGauge) IntGauge { + return IntGauge{orig: orig} +} + +// NewIntGauge creates a new empty IntGauge. +// +// This must be used only in testing code since no "Set" method available. +func NewIntGauge() IntGauge { + return newIntGauge(&otlpmetrics.IntGauge{}) +} + +// DataPoints returns the DataPoints associated with this IntGauge. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntGauge) DataPoints() IntDataPointSlice { + return newIntDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms IntGauge) CopyTo(dest IntGauge) { + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// DoubleGauge represents the type of a double scalar metric that always exports the "current value" for every data point. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleGauge function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleGauge struct { + orig *otlpmetrics.DoubleGauge +} + +func newDoubleGauge(orig *otlpmetrics.DoubleGauge) DoubleGauge { + return DoubleGauge{orig: orig} +} + +// NewDoubleGauge creates a new empty DoubleGauge. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleGauge() DoubleGauge { + return newDoubleGauge(&otlpmetrics.DoubleGauge{}) +} + +// DataPoints returns the DataPoints associated with this DoubleGauge. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleGauge) DataPoints() DoubleDataPointSlice { + return newDoubleDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleGauge) CopyTo(dest DoubleGauge) { + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// IntSum represents the type of a numeric int scalar metric that is calculated as a sum of all reported measurements over a time interval. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntSum function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntSum struct { + orig *otlpmetrics.IntSum +} + +func newIntSum(orig *otlpmetrics.IntSum) IntSum { + return IntSum{orig: orig} +} + +// NewIntSum creates a new empty IntSum. +// +// This must be used only in testing code since no "Set" method available. +func NewIntSum() IntSum { + return newIntSum(&otlpmetrics.IntSum{}) +} + +// AggregationTemporality returns the aggregationtemporality associated with this IntSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntSum) AggregationTemporality() AggregationTemporality { + return AggregationTemporality((*ms.orig).AggregationTemporality) +} + +// SetAggregationTemporality replaces the aggregationtemporality associated with this IntSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntSum) SetAggregationTemporality(v AggregationTemporality) { + (*ms.orig).AggregationTemporality = otlpmetrics.AggregationTemporality(v) +} + +// IsMonotonic returns the ismonotonic associated with this IntSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntSum) IsMonotonic() bool { + return (*ms.orig).IsMonotonic +} + +// SetIsMonotonic replaces the ismonotonic associated with this IntSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntSum) SetIsMonotonic(v bool) { + (*ms.orig).IsMonotonic = v +} + +// DataPoints returns the DataPoints associated with this IntSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntSum) DataPoints() IntDataPointSlice { + return newIntDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms IntSum) CopyTo(dest IntSum) { + dest.SetAggregationTemporality(ms.AggregationTemporality()) + dest.SetIsMonotonic(ms.IsMonotonic()) + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// DoubleSum represents the type of a numeric double scalar metric that is calculated as a sum of all reported measurements over a time interval. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleSum function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleSum struct { + orig *otlpmetrics.DoubleSum +} + +func newDoubleSum(orig *otlpmetrics.DoubleSum) DoubleSum { + return DoubleSum{orig: orig} +} + +// NewDoubleSum creates a new empty DoubleSum. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleSum() DoubleSum { + return newDoubleSum(&otlpmetrics.DoubleSum{}) +} + +// AggregationTemporality returns the aggregationtemporality associated with this DoubleSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSum) AggregationTemporality() AggregationTemporality { + return AggregationTemporality((*ms.orig).AggregationTemporality) +} + +// SetAggregationTemporality replaces the aggregationtemporality associated with this DoubleSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSum) SetAggregationTemporality(v AggregationTemporality) { + (*ms.orig).AggregationTemporality = otlpmetrics.AggregationTemporality(v) +} + +// IsMonotonic returns the ismonotonic associated with this DoubleSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSum) IsMonotonic() bool { + return (*ms.orig).IsMonotonic +} + +// SetIsMonotonic replaces the ismonotonic associated with this DoubleSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSum) SetIsMonotonic(v bool) { + (*ms.orig).IsMonotonic = v +} + +// DataPoints returns the DataPoints associated with this DoubleSum. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSum) DataPoints() DoubleDataPointSlice { + return newDoubleDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleSum) CopyTo(dest DoubleSum) { + dest.SetAggregationTemporality(ms.AggregationTemporality()) + dest.SetIsMonotonic(ms.IsMonotonic()) + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// IntHistogram represents the type of a metric that is calculated by aggregating as a Histogram of all reported double measurements over a time interval. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntHistogram function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntHistogram struct { + orig *otlpmetrics.IntHistogram +} + +func newIntHistogram(orig *otlpmetrics.IntHistogram) IntHistogram { + return IntHistogram{orig: orig} +} + +// NewIntHistogram creates a new empty IntHistogram. +// +// This must be used only in testing code since no "Set" method available. +func NewIntHistogram() IntHistogram { + return newIntHistogram(&otlpmetrics.IntHistogram{}) +} + +// AggregationTemporality returns the aggregationtemporality associated with this IntHistogram. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogram) AggregationTemporality() AggregationTemporality { + return AggregationTemporality((*ms.orig).AggregationTemporality) +} + +// SetAggregationTemporality replaces the aggregationtemporality associated with this IntHistogram. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogram) SetAggregationTemporality(v AggregationTemporality) { + (*ms.orig).AggregationTemporality = otlpmetrics.AggregationTemporality(v) +} + +// DataPoints returns the DataPoints associated with this IntHistogram. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogram) DataPoints() IntHistogramDataPointSlice { + return newIntHistogramDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms IntHistogram) CopyTo(dest IntHistogram) { + dest.SetAggregationTemporality(ms.AggregationTemporality()) + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// DoubleHistogram represents the type of a metric that is calculated by aggregating as a Histogram of all reported double measurements over a time interval. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleHistogram function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleHistogram struct { + orig *otlpmetrics.DoubleHistogram +} + +func newDoubleHistogram(orig *otlpmetrics.DoubleHistogram) DoubleHistogram { + return DoubleHistogram{orig: orig} +} + +// NewDoubleHistogram creates a new empty DoubleHistogram. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleHistogram() DoubleHistogram { + return newDoubleHistogram(&otlpmetrics.DoubleHistogram{}) +} + +// AggregationTemporality returns the aggregationtemporality associated with this DoubleHistogram. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogram) AggregationTemporality() AggregationTemporality { + return AggregationTemporality((*ms.orig).AggregationTemporality) +} + +// SetAggregationTemporality replaces the aggregationtemporality associated with this DoubleHistogram. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogram) SetAggregationTemporality(v AggregationTemporality) { + (*ms.orig).AggregationTemporality = otlpmetrics.AggregationTemporality(v) +} + +// DataPoints returns the DataPoints associated with this DoubleHistogram. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogram) DataPoints() DoubleHistogramDataPointSlice { + return newDoubleHistogramDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleHistogram) CopyTo(dest DoubleHistogram) { + dest.SetAggregationTemporality(ms.AggregationTemporality()) + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// DoubleSummary represents the type of a metric that is calculated by aggregating as a Summary of all reported double measurements over a time interval. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleSummary function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleSummary struct { + orig *otlpmetrics.DoubleSummary +} + +func newDoubleSummary(orig *otlpmetrics.DoubleSummary) DoubleSummary { + return DoubleSummary{orig: orig} +} + +// NewDoubleSummary creates a new empty DoubleSummary. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleSummary() DoubleSummary { + return newDoubleSummary(&otlpmetrics.DoubleSummary{}) +} + +// DataPoints returns the DataPoints associated with this DoubleSummary. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummary) DataPoints() DoubleSummaryDataPointSlice { + return newDoubleSummaryDataPointSlice(&(*ms.orig).DataPoints) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleSummary) CopyTo(dest DoubleSummary) { + ms.DataPoints().CopyTo(dest.DataPoints()) +} + +// IntDataPointSlice logically represents a slice of IntDataPoint. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntDataPointSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntDataPointSlice struct { + // orig points to the slice otlpmetrics.IntDataPoint field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.IntDataPoint +} + +func newIntDataPointSlice(orig *[]*otlpmetrics.IntDataPoint) IntDataPointSlice { + return IntDataPointSlice{orig} +} + +// NewIntDataPointSlice creates a IntDataPointSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewIntDataPointSlice() IntDataPointSlice { + orig := []*otlpmetrics.IntDataPoint(nil) + return IntDataPointSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewIntDataPointSlice()". +func (es IntDataPointSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es IntDataPointSlice) At(ix int) IntDataPoint { + return newIntDataPoint((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es IntDataPointSlice) MoveAndAppendTo(dest IntDataPointSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es IntDataPointSlice) CopyTo(dest IntDataPointSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newIntDataPoint((*es.orig)[i]).CopyTo(newIntDataPoint((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.IntDataPoint, srcLen) + wrappers := make([]*otlpmetrics.IntDataPoint, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newIntDataPoint((*es.orig)[i]).CopyTo(newIntDataPoint(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new IntDataPointSlice can be initialized: +// es := NewIntDataPointSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es IntDataPointSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.IntDataPoint, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.IntDataPoint, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the IntDataPointSlice by one and set the +// given IntDataPoint at that new position. The original IntDataPoint +// could still be referenced so do not reuse it after passing it to this +// method. +func (es IntDataPointSlice) Append(e IntDataPoint) { + *es.orig = append(*es.orig, e.orig) +} + +// IntDataPoint is a single data point in a timeseries that describes the time-varying values of a scalar int metric. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntDataPoint function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntDataPoint struct { + orig *otlpmetrics.IntDataPoint +} + +func newIntDataPoint(orig *otlpmetrics.IntDataPoint) IntDataPoint { + return IntDataPoint{orig: orig} +} + +// NewIntDataPoint creates a new empty IntDataPoint. +// +// This must be used only in testing code since no "Set" method available. +func NewIntDataPoint() IntDataPoint { + return newIntDataPoint(&otlpmetrics.IntDataPoint{}) +} + +// LabelsMap returns the Labels associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) LabelsMap() StringMap { + return newStringMap(&(*ms.orig).Labels) +} + +// StartTime returns the starttime associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) StartTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).StartTimeUnixNano) +} + +// SetStartTime replaces the starttime associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) SetStartTime(v TimestampUnixNano) { + (*ms.orig).StartTimeUnixNano = uint64(v) +} + +// Timestamp returns the timestamp associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Value returns the value associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) Value() int64 { + return (*ms.orig).Value +} + +// SetValue replaces the value associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) SetValue(v int64) { + (*ms.orig).Value = v +} + +// Exemplars returns the Exemplars associated with this IntDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntDataPoint) Exemplars() IntExemplarSlice { + return newIntExemplarSlice(&(*ms.orig).Exemplars) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms IntDataPoint) CopyTo(dest IntDataPoint) { + ms.LabelsMap().CopyTo(dest.LabelsMap()) + dest.SetStartTime(ms.StartTime()) + dest.SetTimestamp(ms.Timestamp()) + dest.SetValue(ms.Value()) + ms.Exemplars().CopyTo(dest.Exemplars()) +} + +// DoubleDataPointSlice logically represents a slice of DoubleDataPoint. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleDataPointSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleDataPointSlice struct { + // orig points to the slice otlpmetrics.DoubleDataPoint field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.DoubleDataPoint +} + +func newDoubleDataPointSlice(orig *[]*otlpmetrics.DoubleDataPoint) DoubleDataPointSlice { + return DoubleDataPointSlice{orig} +} + +// NewDoubleDataPointSlice creates a DoubleDataPointSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewDoubleDataPointSlice() DoubleDataPointSlice { + orig := []*otlpmetrics.DoubleDataPoint(nil) + return DoubleDataPointSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewDoubleDataPointSlice()". +func (es DoubleDataPointSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es DoubleDataPointSlice) At(ix int) DoubleDataPoint { + return newDoubleDataPoint((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es DoubleDataPointSlice) MoveAndAppendTo(dest DoubleDataPointSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es DoubleDataPointSlice) CopyTo(dest DoubleDataPointSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newDoubleDataPoint((*es.orig)[i]).CopyTo(newDoubleDataPoint((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.DoubleDataPoint, srcLen) + wrappers := make([]*otlpmetrics.DoubleDataPoint, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newDoubleDataPoint((*es.orig)[i]).CopyTo(newDoubleDataPoint(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new DoubleDataPointSlice can be initialized: +// es := NewDoubleDataPointSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es DoubleDataPointSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.DoubleDataPoint, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.DoubleDataPoint, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the DoubleDataPointSlice by one and set the +// given DoubleDataPoint at that new position. The original DoubleDataPoint +// could still be referenced so do not reuse it after passing it to this +// method. +func (es DoubleDataPointSlice) Append(e DoubleDataPoint) { + *es.orig = append(*es.orig, e.orig) +} + +// DoubleDataPoint is a single data point in a timeseries that describes the time-varying value of a double metric. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleDataPoint function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleDataPoint struct { + orig *otlpmetrics.DoubleDataPoint +} + +func newDoubleDataPoint(orig *otlpmetrics.DoubleDataPoint) DoubleDataPoint { + return DoubleDataPoint{orig: orig} +} + +// NewDoubleDataPoint creates a new empty DoubleDataPoint. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleDataPoint() DoubleDataPoint { + return newDoubleDataPoint(&otlpmetrics.DoubleDataPoint{}) +} + +// LabelsMap returns the Labels associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) LabelsMap() StringMap { + return newStringMap(&(*ms.orig).Labels) +} + +// StartTime returns the starttime associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) StartTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).StartTimeUnixNano) +} + +// SetStartTime replaces the starttime associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) SetStartTime(v TimestampUnixNano) { + (*ms.orig).StartTimeUnixNano = uint64(v) +} + +// Timestamp returns the timestamp associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Value returns the value associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) Value() float64 { + return (*ms.orig).Value +} + +// SetValue replaces the value associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) SetValue(v float64) { + (*ms.orig).Value = v +} + +// Exemplars returns the Exemplars associated with this DoubleDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleDataPoint) Exemplars() DoubleExemplarSlice { + return newDoubleExemplarSlice(&(*ms.orig).Exemplars) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleDataPoint) CopyTo(dest DoubleDataPoint) { + ms.LabelsMap().CopyTo(dest.LabelsMap()) + dest.SetStartTime(ms.StartTime()) + dest.SetTimestamp(ms.Timestamp()) + dest.SetValue(ms.Value()) + ms.Exemplars().CopyTo(dest.Exemplars()) +} + +// IntHistogramDataPointSlice logically represents a slice of IntHistogramDataPoint. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntHistogramDataPointSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntHistogramDataPointSlice struct { + // orig points to the slice otlpmetrics.IntHistogramDataPoint field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.IntHistogramDataPoint +} + +func newIntHistogramDataPointSlice(orig *[]*otlpmetrics.IntHistogramDataPoint) IntHistogramDataPointSlice { + return IntHistogramDataPointSlice{orig} +} + +// NewIntHistogramDataPointSlice creates a IntHistogramDataPointSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewIntHistogramDataPointSlice() IntHistogramDataPointSlice { + orig := []*otlpmetrics.IntHistogramDataPoint(nil) + return IntHistogramDataPointSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewIntHistogramDataPointSlice()". +func (es IntHistogramDataPointSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es IntHistogramDataPointSlice) At(ix int) IntHistogramDataPoint { + return newIntHistogramDataPoint((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es IntHistogramDataPointSlice) MoveAndAppendTo(dest IntHistogramDataPointSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es IntHistogramDataPointSlice) CopyTo(dest IntHistogramDataPointSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newIntHistogramDataPoint((*es.orig)[i]).CopyTo(newIntHistogramDataPoint((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.IntHistogramDataPoint, srcLen) + wrappers := make([]*otlpmetrics.IntHistogramDataPoint, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newIntHistogramDataPoint((*es.orig)[i]).CopyTo(newIntHistogramDataPoint(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new IntHistogramDataPointSlice can be initialized: +// es := NewIntHistogramDataPointSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es IntHistogramDataPointSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.IntHistogramDataPoint, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.IntHistogramDataPoint, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the IntHistogramDataPointSlice by one and set the +// given IntHistogramDataPoint at that new position. The original IntHistogramDataPoint +// could still be referenced so do not reuse it after passing it to this +// method. +func (es IntHistogramDataPointSlice) Append(e IntHistogramDataPoint) { + *es.orig = append(*es.orig, e.orig) +} + +// IntHistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of int values. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntHistogramDataPoint function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntHistogramDataPoint struct { + orig *otlpmetrics.IntHistogramDataPoint +} + +func newIntHistogramDataPoint(orig *otlpmetrics.IntHistogramDataPoint) IntHistogramDataPoint { + return IntHistogramDataPoint{orig: orig} +} + +// NewIntHistogramDataPoint creates a new empty IntHistogramDataPoint. +// +// This must be used only in testing code since no "Set" method available. +func NewIntHistogramDataPoint() IntHistogramDataPoint { + return newIntHistogramDataPoint(&otlpmetrics.IntHistogramDataPoint{}) +} + +// LabelsMap returns the Labels associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) LabelsMap() StringMap { + return newStringMap(&(*ms.orig).Labels) +} + +// StartTime returns the starttime associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) StartTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).StartTimeUnixNano) +} + +// SetStartTime replaces the starttime associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) SetStartTime(v TimestampUnixNano) { + (*ms.orig).StartTimeUnixNano = uint64(v) +} + +// Timestamp returns the timestamp associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Count returns the count associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) Count() uint64 { + return (*ms.orig).Count +} + +// SetCount replaces the count associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) SetCount(v uint64) { + (*ms.orig).Count = v +} + +// Sum returns the sum associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) Sum() int64 { + return (*ms.orig).Sum +} + +// SetSum replaces the sum associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) SetSum(v int64) { + (*ms.orig).Sum = v +} + +// BucketCounts returns the bucketcounts associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) BucketCounts() []uint64 { + return (*ms.orig).BucketCounts +} + +// SetBucketCounts replaces the bucketcounts associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) SetBucketCounts(v []uint64) { + (*ms.orig).BucketCounts = v +} + +// ExplicitBounds returns the explicitbounds associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) ExplicitBounds() []float64 { + return (*ms.orig).ExplicitBounds +} + +// SetExplicitBounds replaces the explicitbounds associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) SetExplicitBounds(v []float64) { + (*ms.orig).ExplicitBounds = v +} + +// Exemplars returns the Exemplars associated with this IntHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntHistogramDataPoint) Exemplars() IntExemplarSlice { + return newIntExemplarSlice(&(*ms.orig).Exemplars) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms IntHistogramDataPoint) CopyTo(dest IntHistogramDataPoint) { + ms.LabelsMap().CopyTo(dest.LabelsMap()) + dest.SetStartTime(ms.StartTime()) + dest.SetTimestamp(ms.Timestamp()) + dest.SetCount(ms.Count()) + dest.SetSum(ms.Sum()) + dest.SetBucketCounts(ms.BucketCounts()) + dest.SetExplicitBounds(ms.ExplicitBounds()) + ms.Exemplars().CopyTo(dest.Exemplars()) +} + +// DoubleHistogramDataPointSlice logically represents a slice of DoubleHistogramDataPoint. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleHistogramDataPointSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleHistogramDataPointSlice struct { + // orig points to the slice otlpmetrics.DoubleHistogramDataPoint field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.DoubleHistogramDataPoint +} + +func newDoubleHistogramDataPointSlice(orig *[]*otlpmetrics.DoubleHistogramDataPoint) DoubleHistogramDataPointSlice { + return DoubleHistogramDataPointSlice{orig} +} + +// NewDoubleHistogramDataPointSlice creates a DoubleHistogramDataPointSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewDoubleHistogramDataPointSlice() DoubleHistogramDataPointSlice { + orig := []*otlpmetrics.DoubleHistogramDataPoint(nil) + return DoubleHistogramDataPointSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewDoubleHistogramDataPointSlice()". +func (es DoubleHistogramDataPointSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es DoubleHistogramDataPointSlice) At(ix int) DoubleHistogramDataPoint { + return newDoubleHistogramDataPoint((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es DoubleHistogramDataPointSlice) MoveAndAppendTo(dest DoubleHistogramDataPointSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es DoubleHistogramDataPointSlice) CopyTo(dest DoubleHistogramDataPointSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newDoubleHistogramDataPoint((*es.orig)[i]).CopyTo(newDoubleHistogramDataPoint((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.DoubleHistogramDataPoint, srcLen) + wrappers := make([]*otlpmetrics.DoubleHistogramDataPoint, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newDoubleHistogramDataPoint((*es.orig)[i]).CopyTo(newDoubleHistogramDataPoint(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new DoubleHistogramDataPointSlice can be initialized: +// es := NewDoubleHistogramDataPointSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es DoubleHistogramDataPointSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.DoubleHistogramDataPoint, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.DoubleHistogramDataPoint, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the DoubleHistogramDataPointSlice by one and set the +// given DoubleHistogramDataPoint at that new position. The original DoubleHistogramDataPoint +// could still be referenced so do not reuse it after passing it to this +// method. +func (es DoubleHistogramDataPointSlice) Append(e DoubleHistogramDataPoint) { + *es.orig = append(*es.orig, e.orig) +} + +// DoubleHistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of double values. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleHistogramDataPoint function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleHistogramDataPoint struct { + orig *otlpmetrics.DoubleHistogramDataPoint +} + +func newDoubleHistogramDataPoint(orig *otlpmetrics.DoubleHistogramDataPoint) DoubleHistogramDataPoint { + return DoubleHistogramDataPoint{orig: orig} +} + +// NewDoubleHistogramDataPoint creates a new empty DoubleHistogramDataPoint. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleHistogramDataPoint() DoubleHistogramDataPoint { + return newDoubleHistogramDataPoint(&otlpmetrics.DoubleHistogramDataPoint{}) +} + +// LabelsMap returns the Labels associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) LabelsMap() StringMap { + return newStringMap(&(*ms.orig).Labels) +} + +// StartTime returns the starttime associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) StartTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).StartTimeUnixNano) +} + +// SetStartTime replaces the starttime associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) SetStartTime(v TimestampUnixNano) { + (*ms.orig).StartTimeUnixNano = uint64(v) +} + +// Timestamp returns the timestamp associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Count returns the count associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) Count() uint64 { + return (*ms.orig).Count +} + +// SetCount replaces the count associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) SetCount(v uint64) { + (*ms.orig).Count = v +} + +// Sum returns the sum associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) Sum() float64 { + return (*ms.orig).Sum +} + +// SetSum replaces the sum associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) SetSum(v float64) { + (*ms.orig).Sum = v +} + +// BucketCounts returns the bucketcounts associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) BucketCounts() []uint64 { + return (*ms.orig).BucketCounts +} + +// SetBucketCounts replaces the bucketcounts associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) SetBucketCounts(v []uint64) { + (*ms.orig).BucketCounts = v +} + +// ExplicitBounds returns the explicitbounds associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) ExplicitBounds() []float64 { + return (*ms.orig).ExplicitBounds +} + +// SetExplicitBounds replaces the explicitbounds associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) SetExplicitBounds(v []float64) { + (*ms.orig).ExplicitBounds = v +} + +// Exemplars returns the Exemplars associated with this DoubleHistogramDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleHistogramDataPoint) Exemplars() DoubleExemplarSlice { + return newDoubleExemplarSlice(&(*ms.orig).Exemplars) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleHistogramDataPoint) CopyTo(dest DoubleHistogramDataPoint) { + ms.LabelsMap().CopyTo(dest.LabelsMap()) + dest.SetStartTime(ms.StartTime()) + dest.SetTimestamp(ms.Timestamp()) + dest.SetCount(ms.Count()) + dest.SetSum(ms.Sum()) + dest.SetBucketCounts(ms.BucketCounts()) + dest.SetExplicitBounds(ms.ExplicitBounds()) + ms.Exemplars().CopyTo(dest.Exemplars()) +} + +// DoubleSummaryDataPointSlice logically represents a slice of DoubleSummaryDataPoint. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleSummaryDataPointSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleSummaryDataPointSlice struct { + // orig points to the slice otlpmetrics.DoubleSummaryDataPoint field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.DoubleSummaryDataPoint +} + +func newDoubleSummaryDataPointSlice(orig *[]*otlpmetrics.DoubleSummaryDataPoint) DoubleSummaryDataPointSlice { + return DoubleSummaryDataPointSlice{orig} +} + +// NewDoubleSummaryDataPointSlice creates a DoubleSummaryDataPointSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewDoubleSummaryDataPointSlice() DoubleSummaryDataPointSlice { + orig := []*otlpmetrics.DoubleSummaryDataPoint(nil) + return DoubleSummaryDataPointSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewDoubleSummaryDataPointSlice()". +func (es DoubleSummaryDataPointSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es DoubleSummaryDataPointSlice) At(ix int) DoubleSummaryDataPoint { + return newDoubleSummaryDataPoint((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es DoubleSummaryDataPointSlice) MoveAndAppendTo(dest DoubleSummaryDataPointSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es DoubleSummaryDataPointSlice) CopyTo(dest DoubleSummaryDataPointSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newDoubleSummaryDataPoint((*es.orig)[i]).CopyTo(newDoubleSummaryDataPoint((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.DoubleSummaryDataPoint, srcLen) + wrappers := make([]*otlpmetrics.DoubleSummaryDataPoint, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newDoubleSummaryDataPoint((*es.orig)[i]).CopyTo(newDoubleSummaryDataPoint(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new DoubleSummaryDataPointSlice can be initialized: +// es := NewDoubleSummaryDataPointSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es DoubleSummaryDataPointSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.DoubleSummaryDataPoint, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.DoubleSummaryDataPoint, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the DoubleSummaryDataPointSlice by one and set the +// given DoubleSummaryDataPoint at that new position. The original DoubleSummaryDataPoint +// could still be referenced so do not reuse it after passing it to this +// method. +func (es DoubleSummaryDataPointSlice) Append(e DoubleSummaryDataPoint) { + *es.orig = append(*es.orig, e.orig) +} + +// DoubleSummaryDataPoint is a single data point in a timeseries that describes the time-varying values of a Summary of double values. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleSummaryDataPoint function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleSummaryDataPoint struct { + orig *otlpmetrics.DoubleSummaryDataPoint +} + +func newDoubleSummaryDataPoint(orig *otlpmetrics.DoubleSummaryDataPoint) DoubleSummaryDataPoint { + return DoubleSummaryDataPoint{orig: orig} +} + +// NewDoubleSummaryDataPoint creates a new empty DoubleSummaryDataPoint. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleSummaryDataPoint() DoubleSummaryDataPoint { + return newDoubleSummaryDataPoint(&otlpmetrics.DoubleSummaryDataPoint{}) +} + +// LabelsMap returns the Labels associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) LabelsMap() StringMap { + return newStringMap(&(*ms.orig).Labels) +} + +// StartTime returns the starttime associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) StartTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).StartTimeUnixNano) +} + +// SetStartTime replaces the starttime associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) SetStartTime(v TimestampUnixNano) { + (*ms.orig).StartTimeUnixNano = uint64(v) +} + +// Timestamp returns the timestamp associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Count returns the count associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) Count() uint64 { + return (*ms.orig).Count +} + +// SetCount replaces the count associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) SetCount(v uint64) { + (*ms.orig).Count = v +} + +// Sum returns the sum associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) Sum() float64 { + return (*ms.orig).Sum +} + +// SetSum replaces the sum associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) SetSum(v float64) { + (*ms.orig).Sum = v +} + +// QuantileValues returns the QuantileValues associated with this DoubleSummaryDataPoint. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleSummaryDataPoint) QuantileValues() ValueAtQuantileSlice { + return newValueAtQuantileSlice(&(*ms.orig).QuantileValues) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleSummaryDataPoint) CopyTo(dest DoubleSummaryDataPoint) { + ms.LabelsMap().CopyTo(dest.LabelsMap()) + dest.SetStartTime(ms.StartTime()) + dest.SetTimestamp(ms.Timestamp()) + dest.SetCount(ms.Count()) + dest.SetSum(ms.Sum()) + ms.QuantileValues().CopyTo(dest.QuantileValues()) +} + +// ValueAtQuantileSlice logically represents a slice of ValueAtQuantile. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewValueAtQuantileSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ValueAtQuantileSlice struct { + // orig points to the slice otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile +} + +func newValueAtQuantileSlice(orig *[]*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile) ValueAtQuantileSlice { + return ValueAtQuantileSlice{orig} +} + +// NewValueAtQuantileSlice creates a ValueAtQuantileSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewValueAtQuantileSlice() ValueAtQuantileSlice { + orig := []*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile(nil) + return ValueAtQuantileSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewValueAtQuantileSlice()". +func (es ValueAtQuantileSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es ValueAtQuantileSlice) At(ix int) ValueAtQuantile { + return newValueAtQuantile((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es ValueAtQuantileSlice) MoveAndAppendTo(dest ValueAtQuantileSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es ValueAtQuantileSlice) CopyTo(dest ValueAtQuantileSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newValueAtQuantile((*es.orig)[i]).CopyTo(newValueAtQuantile((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile, srcLen) + wrappers := make([]*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newValueAtQuantile((*es.orig)[i]).CopyTo(newValueAtQuantile(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new ValueAtQuantileSlice can be initialized: +// es := NewValueAtQuantileSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es ValueAtQuantileSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the ValueAtQuantileSlice by one and set the +// given ValueAtQuantile at that new position. The original ValueAtQuantile +// could still be referenced so do not reuse it after passing it to this +// method. +func (es ValueAtQuantileSlice) Append(e ValueAtQuantile) { + *es.orig = append(*es.orig, e.orig) +} + +// ValueAtQuantile is a quantile value within a Summary data point +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewValueAtQuantile function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ValueAtQuantile struct { + orig *otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile +} + +func newValueAtQuantile(orig *otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile) ValueAtQuantile { + return ValueAtQuantile{orig: orig} +} + +// NewValueAtQuantile creates a new empty ValueAtQuantile. +// +// This must be used only in testing code since no "Set" method available. +func NewValueAtQuantile() ValueAtQuantile { + return newValueAtQuantile(&otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile{}) +} + +// Quantile returns the quantile associated with this ValueAtQuantile. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ValueAtQuantile) Quantile() float64 { + return (*ms.orig).Quantile +} + +// SetQuantile replaces the quantile associated with this ValueAtQuantile. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ValueAtQuantile) SetQuantile(v float64) { + (*ms.orig).Quantile = v +} + +// Value returns the value associated with this ValueAtQuantile. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ValueAtQuantile) Value() float64 { + return (*ms.orig).Value +} + +// SetValue replaces the value associated with this ValueAtQuantile. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ValueAtQuantile) SetValue(v float64) { + (*ms.orig).Value = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms ValueAtQuantile) CopyTo(dest ValueAtQuantile) { + dest.SetQuantile(ms.Quantile()) + dest.SetValue(ms.Value()) +} + +// IntExemplarSlice logically represents a slice of IntExemplar. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntExemplarSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntExemplarSlice struct { + // orig points to the slice otlpmetrics.IntExemplar field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.IntExemplar +} + +func newIntExemplarSlice(orig *[]*otlpmetrics.IntExemplar) IntExemplarSlice { + return IntExemplarSlice{orig} +} + +// NewIntExemplarSlice creates a IntExemplarSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewIntExemplarSlice() IntExemplarSlice { + orig := []*otlpmetrics.IntExemplar(nil) + return IntExemplarSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewIntExemplarSlice()". +func (es IntExemplarSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es IntExemplarSlice) At(ix int) IntExemplar { + return newIntExemplar((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es IntExemplarSlice) MoveAndAppendTo(dest IntExemplarSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es IntExemplarSlice) CopyTo(dest IntExemplarSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newIntExemplar((*es.orig)[i]).CopyTo(newIntExemplar((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.IntExemplar, srcLen) + wrappers := make([]*otlpmetrics.IntExemplar, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newIntExemplar((*es.orig)[i]).CopyTo(newIntExemplar(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new IntExemplarSlice can be initialized: +// es := NewIntExemplarSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es IntExemplarSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.IntExemplar, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.IntExemplar, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the IntExemplarSlice by one and set the +// given IntExemplar at that new position. The original IntExemplar +// could still be referenced so do not reuse it after passing it to this +// method. +func (es IntExemplarSlice) Append(e IntExemplar) { + *es.orig = append(*es.orig, e.orig) +} + +// IntExemplar is a sample input int measurement. +// +// Exemplars also hold information about the environment when the measurement was recorded, +// for example the span and trace ID of the active span when the exemplar was recorded. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewIntExemplar function to create new instances. +// Important: zero-initialized instance is not valid for use. +type IntExemplar struct { + orig *otlpmetrics.IntExemplar +} + +func newIntExemplar(orig *otlpmetrics.IntExemplar) IntExemplar { + return IntExemplar{orig: orig} +} + +// NewIntExemplar creates a new empty IntExemplar. +// +// This must be used only in testing code since no "Set" method available. +func NewIntExemplar() IntExemplar { + return newIntExemplar(&otlpmetrics.IntExemplar{}) +} + +// Timestamp returns the timestamp associated with this IntExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntExemplar) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this IntExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntExemplar) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Value returns the value associated with this IntExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntExemplar) Value() int64 { + return (*ms.orig).Value +} + +// SetValue replaces the value associated with this IntExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntExemplar) SetValue(v int64) { + (*ms.orig).Value = v +} + +// FilteredLabels returns the FilteredLabels associated with this IntExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms IntExemplar) FilteredLabels() StringMap { + return newStringMap(&(*ms.orig).FilteredLabels) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms IntExemplar) CopyTo(dest IntExemplar) { + dest.SetTimestamp(ms.Timestamp()) + dest.SetValue(ms.Value()) + ms.FilteredLabels().CopyTo(dest.FilteredLabels()) +} + +// DoubleExemplarSlice logically represents a slice of DoubleExemplar. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleExemplarSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleExemplarSlice struct { + // orig points to the slice otlpmetrics.DoubleExemplar field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlpmetrics.DoubleExemplar +} + +func newDoubleExemplarSlice(orig *[]*otlpmetrics.DoubleExemplar) DoubleExemplarSlice { + return DoubleExemplarSlice{orig} +} + +// NewDoubleExemplarSlice creates a DoubleExemplarSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewDoubleExemplarSlice() DoubleExemplarSlice { + orig := []*otlpmetrics.DoubleExemplar(nil) + return DoubleExemplarSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewDoubleExemplarSlice()". +func (es DoubleExemplarSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es DoubleExemplarSlice) At(ix int) DoubleExemplar { + return newDoubleExemplar((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es DoubleExemplarSlice) MoveAndAppendTo(dest DoubleExemplarSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es DoubleExemplarSlice) CopyTo(dest DoubleExemplarSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newDoubleExemplar((*es.orig)[i]).CopyTo(newDoubleExemplar((*dest.orig)[i])) + } + return + } + origs := make([]otlpmetrics.DoubleExemplar, srcLen) + wrappers := make([]*otlpmetrics.DoubleExemplar, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newDoubleExemplar((*es.orig)[i]).CopyTo(newDoubleExemplar(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new DoubleExemplarSlice can be initialized: +// es := NewDoubleExemplarSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es DoubleExemplarSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlpmetrics.DoubleExemplar, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlpmetrics.DoubleExemplar, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the DoubleExemplarSlice by one and set the +// given DoubleExemplar at that new position. The original DoubleExemplar +// could still be referenced so do not reuse it after passing it to this +// method. +func (es DoubleExemplarSlice) Append(e DoubleExemplar) { + *es.orig = append(*es.orig, e.orig) +} + +// DoubleExemplar is a sample input double measurement. +// +// Exemplars also hold information about the environment when the measurement was recorded, +// for example the span and trace ID of the active span when the exemplar was recorded. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewDoubleExemplar function to create new instances. +// Important: zero-initialized instance is not valid for use. +type DoubleExemplar struct { + orig *otlpmetrics.DoubleExemplar +} + +func newDoubleExemplar(orig *otlpmetrics.DoubleExemplar) DoubleExemplar { + return DoubleExemplar{orig: orig} +} + +// NewDoubleExemplar creates a new empty DoubleExemplar. +// +// This must be used only in testing code since no "Set" method available. +func NewDoubleExemplar() DoubleExemplar { + return newDoubleExemplar(&otlpmetrics.DoubleExemplar{}) +} + +// Timestamp returns the timestamp associated with this DoubleExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleExemplar) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this DoubleExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleExemplar) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Value returns the value associated with this DoubleExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleExemplar) Value() float64 { + return (*ms.orig).Value +} + +// SetValue replaces the value associated with this DoubleExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleExemplar) SetValue(v float64) { + (*ms.orig).Value = v +} + +// FilteredLabels returns the FilteredLabels associated with this DoubleExemplar. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms DoubleExemplar) FilteredLabels() StringMap { + return newStringMap(&(*ms.orig).FilteredLabels) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms DoubleExemplar) CopyTo(dest DoubleExemplar) { + dest.SetTimestamp(ms.Timestamp()) + dest.SetValue(ms.Value()) + ms.FilteredLabels().CopyTo(dest.FilteredLabels()) +} diff --git a/internal/otel_collector/consumer/pdata/generated_metrics_test.go b/internal/otel_collector/consumer/pdata/generated_metrics_test.go new file mode 100644 index 00000000000..c99bce13092 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_metrics_test.go @@ -0,0 +1,2220 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +func TestResourceMetricsSlice(t *testing.T) { + es := NewResourceMetricsSlice() + assert.EqualValues(t, 0, es.Len()) + es = newResourceMetricsSlice(&[]*otlpmetrics.ResourceMetrics{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewResourceMetrics() + testVal := generateTestResourceMetrics() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestResourceMetrics(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestResourceMetricsSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestResourceMetricsSlice() + dest := NewResourceMetricsSlice() + src := generateTestResourceMetricsSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestResourceMetricsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestResourceMetricsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestResourceMetricsSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestResourceMetricsSlice_CopyTo(t *testing.T) { + dest := NewResourceMetricsSlice() + // Test CopyTo to empty + NewResourceMetricsSlice().CopyTo(dest) + assert.EqualValues(t, NewResourceMetricsSlice(), dest) + + // Test CopyTo larger slice + generateTestResourceMetricsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestResourceMetricsSlice(), dest) + + // Test CopyTo same size slice + generateTestResourceMetricsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestResourceMetricsSlice(), dest) +} + +func TestResourceMetricsSlice_Resize(t *testing.T) { + es := generateTestResourceMetricsSlice() + emptyVal := NewResourceMetrics() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.ResourceMetrics]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.ResourceMetrics]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.ResourceMetrics]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.ResourceMetrics]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestResourceMetricsSlice_Append(t *testing.T) { + es := generateTestResourceMetricsSlice() + + emptyVal := NewResourceMetrics() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewResourceMetrics() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestResourceMetrics_CopyTo(t *testing.T) { + ms := NewResourceMetrics() + generateTestResourceMetrics().CopyTo(ms) + assert.EqualValues(t, generateTestResourceMetrics(), ms) +} + +func TestResourceMetrics_Resource(t *testing.T) { + ms := NewResourceMetrics() + fillTestResource(ms.Resource()) + assert.EqualValues(t, generateTestResource(), ms.Resource()) +} + +func TestResourceMetrics_InstrumentationLibraryMetrics(t *testing.T) { + ms := NewResourceMetrics() + assert.EqualValues(t, NewInstrumentationLibraryMetricsSlice(), ms.InstrumentationLibraryMetrics()) + fillTestInstrumentationLibraryMetricsSlice(ms.InstrumentationLibraryMetrics()) + testValInstrumentationLibraryMetrics := generateTestInstrumentationLibraryMetricsSlice() + assert.EqualValues(t, testValInstrumentationLibraryMetrics, ms.InstrumentationLibraryMetrics()) +} + +func TestInstrumentationLibraryMetricsSlice(t *testing.T) { + es := NewInstrumentationLibraryMetricsSlice() + assert.EqualValues(t, 0, es.Len()) + es = newInstrumentationLibraryMetricsSlice(&[]*otlpmetrics.InstrumentationLibraryMetrics{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewInstrumentationLibraryMetrics() + testVal := generateTestInstrumentationLibraryMetrics() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestInstrumentationLibraryMetrics(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestInstrumentationLibraryMetricsSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestInstrumentationLibraryMetricsSlice() + dest := NewInstrumentationLibraryMetricsSlice() + src := generateTestInstrumentationLibraryMetricsSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryMetricsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryMetricsSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestInstrumentationLibraryMetricsSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestInstrumentationLibraryMetricsSlice_CopyTo(t *testing.T) { + dest := NewInstrumentationLibraryMetricsSlice() + // Test CopyTo to empty + NewInstrumentationLibraryMetricsSlice().CopyTo(dest) + assert.EqualValues(t, NewInstrumentationLibraryMetricsSlice(), dest) + + // Test CopyTo larger slice + generateTestInstrumentationLibraryMetricsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryMetricsSlice(), dest) + + // Test CopyTo same size slice + generateTestInstrumentationLibraryMetricsSlice().CopyTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibraryMetricsSlice(), dest) +} + +func TestInstrumentationLibraryMetricsSlice_Resize(t *testing.T) { + es := generateTestInstrumentationLibraryMetricsSlice() + emptyVal := NewInstrumentationLibraryMetrics() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.InstrumentationLibraryMetrics]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.InstrumentationLibraryMetrics]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.InstrumentationLibraryMetrics]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.InstrumentationLibraryMetrics]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestInstrumentationLibraryMetricsSlice_Append(t *testing.T) { + es := generateTestInstrumentationLibraryMetricsSlice() + + emptyVal := NewInstrumentationLibraryMetrics() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewInstrumentationLibraryMetrics() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestInstrumentationLibraryMetrics_CopyTo(t *testing.T) { + ms := NewInstrumentationLibraryMetrics() + generateTestInstrumentationLibraryMetrics().CopyTo(ms) + assert.EqualValues(t, generateTestInstrumentationLibraryMetrics(), ms) +} + +func TestInstrumentationLibraryMetrics_InstrumentationLibrary(t *testing.T) { + ms := NewInstrumentationLibraryMetrics() + fillTestInstrumentationLibrary(ms.InstrumentationLibrary()) + assert.EqualValues(t, generateTestInstrumentationLibrary(), ms.InstrumentationLibrary()) +} + +func TestInstrumentationLibraryMetrics_Metrics(t *testing.T) { + ms := NewInstrumentationLibraryMetrics() + assert.EqualValues(t, NewMetricSlice(), ms.Metrics()) + fillTestMetricSlice(ms.Metrics()) + testValMetrics := generateTestMetricSlice() + assert.EqualValues(t, testValMetrics, ms.Metrics()) +} + +func TestMetricSlice(t *testing.T) { + es := NewMetricSlice() + assert.EqualValues(t, 0, es.Len()) + es = newMetricSlice(&[]*otlpmetrics.Metric{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewMetric() + testVal := generateTestMetric() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestMetric(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestMetricSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestMetricSlice() + dest := NewMetricSlice() + src := generateTestMetricSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestMetricSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestMetricSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestMetricSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestMetricSlice_CopyTo(t *testing.T) { + dest := NewMetricSlice() + // Test CopyTo to empty + NewMetricSlice().CopyTo(dest) + assert.EqualValues(t, NewMetricSlice(), dest) + + // Test CopyTo larger slice + generateTestMetricSlice().CopyTo(dest) + assert.EqualValues(t, generateTestMetricSlice(), dest) + + // Test CopyTo same size slice + generateTestMetricSlice().CopyTo(dest) + assert.EqualValues(t, generateTestMetricSlice(), dest) +} + +func TestMetricSlice_Resize(t *testing.T) { + es := generateTestMetricSlice() + emptyVal := NewMetric() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.Metric]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.Metric]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.Metric]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.Metric]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestMetricSlice_Append(t *testing.T) { + es := generateTestMetricSlice() + + emptyVal := NewMetric() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewMetric() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestMetric_CopyTo(t *testing.T) { + ms := NewMetric() + generateTestMetric().CopyTo(ms) + assert.EqualValues(t, generateTestMetric(), ms) +} + +func TestMetric_Name(t *testing.T) { + ms := NewMetric() + assert.EqualValues(t, "", ms.Name()) + testValName := "test_name" + ms.SetName(testValName) + assert.EqualValues(t, testValName, ms.Name()) +} + +func TestMetric_Description(t *testing.T) { + ms := NewMetric() + assert.EqualValues(t, "", ms.Description()) + testValDescription := "test_description" + ms.SetDescription(testValDescription) + assert.EqualValues(t, testValDescription, ms.Description()) +} + +func TestMetric_Unit(t *testing.T) { + ms := NewMetric() + assert.EqualValues(t, "", ms.Unit()) + testValUnit := "1" + ms.SetUnit(testValUnit) + assert.EqualValues(t, testValUnit, ms.Unit()) +} + +func TestIntGauge_CopyTo(t *testing.T) { + ms := NewIntGauge() + generateTestIntGauge().CopyTo(ms) + assert.EqualValues(t, generateTestIntGauge(), ms) +} + +func TestIntGauge_DataPoints(t *testing.T) { + ms := NewIntGauge() + assert.EqualValues(t, NewIntDataPointSlice(), ms.DataPoints()) + fillTestIntDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestIntDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestDoubleGauge_CopyTo(t *testing.T) { + ms := NewDoubleGauge() + generateTestDoubleGauge().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleGauge(), ms) +} + +func TestDoubleGauge_DataPoints(t *testing.T) { + ms := NewDoubleGauge() + assert.EqualValues(t, NewDoubleDataPointSlice(), ms.DataPoints()) + fillTestDoubleDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestDoubleDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestIntSum_CopyTo(t *testing.T) { + ms := NewIntSum() + generateTestIntSum().CopyTo(ms) + assert.EqualValues(t, generateTestIntSum(), ms) +} + +func TestIntSum_AggregationTemporality(t *testing.T) { + ms := NewIntSum() + assert.EqualValues(t, AggregationTemporalityUnspecified, ms.AggregationTemporality()) + testValAggregationTemporality := AggregationTemporalityCumulative + ms.SetAggregationTemporality(testValAggregationTemporality) + assert.EqualValues(t, testValAggregationTemporality, ms.AggregationTemporality()) +} + +func TestIntSum_IsMonotonic(t *testing.T) { + ms := NewIntSum() + assert.EqualValues(t, false, ms.IsMonotonic()) + testValIsMonotonic := true + ms.SetIsMonotonic(testValIsMonotonic) + assert.EqualValues(t, testValIsMonotonic, ms.IsMonotonic()) +} + +func TestIntSum_DataPoints(t *testing.T) { + ms := NewIntSum() + assert.EqualValues(t, NewIntDataPointSlice(), ms.DataPoints()) + fillTestIntDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestIntDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestDoubleSum_CopyTo(t *testing.T) { + ms := NewDoubleSum() + generateTestDoubleSum().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleSum(), ms) +} + +func TestDoubleSum_AggregationTemporality(t *testing.T) { + ms := NewDoubleSum() + assert.EqualValues(t, AggregationTemporalityUnspecified, ms.AggregationTemporality()) + testValAggregationTemporality := AggregationTemporalityCumulative + ms.SetAggregationTemporality(testValAggregationTemporality) + assert.EqualValues(t, testValAggregationTemporality, ms.AggregationTemporality()) +} + +func TestDoubleSum_IsMonotonic(t *testing.T) { + ms := NewDoubleSum() + assert.EqualValues(t, false, ms.IsMonotonic()) + testValIsMonotonic := true + ms.SetIsMonotonic(testValIsMonotonic) + assert.EqualValues(t, testValIsMonotonic, ms.IsMonotonic()) +} + +func TestDoubleSum_DataPoints(t *testing.T) { + ms := NewDoubleSum() + assert.EqualValues(t, NewDoubleDataPointSlice(), ms.DataPoints()) + fillTestDoubleDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestDoubleDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestIntHistogram_CopyTo(t *testing.T) { + ms := NewIntHistogram() + generateTestIntHistogram().CopyTo(ms) + assert.EqualValues(t, generateTestIntHistogram(), ms) +} + +func TestIntHistogram_AggregationTemporality(t *testing.T) { + ms := NewIntHistogram() + assert.EqualValues(t, AggregationTemporalityUnspecified, ms.AggregationTemporality()) + testValAggregationTemporality := AggregationTemporalityCumulative + ms.SetAggregationTemporality(testValAggregationTemporality) + assert.EqualValues(t, testValAggregationTemporality, ms.AggregationTemporality()) +} + +func TestIntHistogram_DataPoints(t *testing.T) { + ms := NewIntHistogram() + assert.EqualValues(t, NewIntHistogramDataPointSlice(), ms.DataPoints()) + fillTestIntHistogramDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestIntHistogramDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestDoubleHistogram_CopyTo(t *testing.T) { + ms := NewDoubleHistogram() + generateTestDoubleHistogram().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleHistogram(), ms) +} + +func TestDoubleHistogram_AggregationTemporality(t *testing.T) { + ms := NewDoubleHistogram() + assert.EqualValues(t, AggregationTemporalityUnspecified, ms.AggregationTemporality()) + testValAggregationTemporality := AggregationTemporalityCumulative + ms.SetAggregationTemporality(testValAggregationTemporality) + assert.EqualValues(t, testValAggregationTemporality, ms.AggregationTemporality()) +} + +func TestDoubleHistogram_DataPoints(t *testing.T) { + ms := NewDoubleHistogram() + assert.EqualValues(t, NewDoubleHistogramDataPointSlice(), ms.DataPoints()) + fillTestDoubleHistogramDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestDoubleHistogramDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestDoubleSummary_CopyTo(t *testing.T) { + ms := NewDoubleSummary() + generateTestDoubleSummary().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleSummary(), ms) +} + +func TestDoubleSummary_DataPoints(t *testing.T) { + ms := NewDoubleSummary() + assert.EqualValues(t, NewDoubleSummaryDataPointSlice(), ms.DataPoints()) + fillTestDoubleSummaryDataPointSlice(ms.DataPoints()) + testValDataPoints := generateTestDoubleSummaryDataPointSlice() + assert.EqualValues(t, testValDataPoints, ms.DataPoints()) +} + +func TestIntDataPointSlice(t *testing.T) { + es := NewIntDataPointSlice() + assert.EqualValues(t, 0, es.Len()) + es = newIntDataPointSlice(&[]*otlpmetrics.IntDataPoint{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewIntDataPoint() + testVal := generateTestIntDataPoint() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestIntDataPoint(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestIntDataPointSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestIntDataPointSlice() + dest := NewIntDataPointSlice() + src := generateTestIntDataPointSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestIntDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestIntDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestIntDataPointSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestIntDataPointSlice_CopyTo(t *testing.T) { + dest := NewIntDataPointSlice() + // Test CopyTo to empty + NewIntDataPointSlice().CopyTo(dest) + assert.EqualValues(t, NewIntDataPointSlice(), dest) + + // Test CopyTo larger slice + generateTestIntDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestIntDataPointSlice(), dest) + + // Test CopyTo same size slice + generateTestIntDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestIntDataPointSlice(), dest) +} + +func TestIntDataPointSlice_Resize(t *testing.T) { + es := generateTestIntDataPointSlice() + emptyVal := NewIntDataPoint() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.IntDataPoint]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.IntDataPoint]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.IntDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.IntDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestIntDataPointSlice_Append(t *testing.T) { + es := generateTestIntDataPointSlice() + + emptyVal := NewIntDataPoint() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewIntDataPoint() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestIntDataPoint_CopyTo(t *testing.T) { + ms := NewIntDataPoint() + generateTestIntDataPoint().CopyTo(ms) + assert.EqualValues(t, generateTestIntDataPoint(), ms) +} + +func TestIntDataPoint_LabelsMap(t *testing.T) { + ms := NewIntDataPoint() + assert.EqualValues(t, NewStringMap(), ms.LabelsMap()) + fillTestStringMap(ms.LabelsMap()) + testValLabelsMap := generateTestStringMap() + assert.EqualValues(t, testValLabelsMap, ms.LabelsMap()) +} + +func TestIntDataPoint_StartTime(t *testing.T) { + ms := NewIntDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.StartTime()) + testValStartTime := TimestampUnixNano(1234567890) + ms.SetStartTime(testValStartTime) + assert.EqualValues(t, testValStartTime, ms.StartTime()) +} + +func TestIntDataPoint_Timestamp(t *testing.T) { + ms := NewIntDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestIntDataPoint_Value(t *testing.T) { + ms := NewIntDataPoint() + assert.EqualValues(t, int64(0), ms.Value()) + testValValue := int64(-17) + ms.SetValue(testValValue) + assert.EqualValues(t, testValValue, ms.Value()) +} + +func TestIntDataPoint_Exemplars(t *testing.T) { + ms := NewIntDataPoint() + assert.EqualValues(t, NewIntExemplarSlice(), ms.Exemplars()) + fillTestIntExemplarSlice(ms.Exemplars()) + testValExemplars := generateTestIntExemplarSlice() + assert.EqualValues(t, testValExemplars, ms.Exemplars()) +} + +func TestDoubleDataPointSlice(t *testing.T) { + es := NewDoubleDataPointSlice() + assert.EqualValues(t, 0, es.Len()) + es = newDoubleDataPointSlice(&[]*otlpmetrics.DoubleDataPoint{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewDoubleDataPoint() + testVal := generateTestDoubleDataPoint() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestDoubleDataPoint(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestDoubleDataPointSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestDoubleDataPointSlice() + dest := NewDoubleDataPointSlice() + src := generateTestDoubleDataPointSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestDoubleDataPointSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestDoubleDataPointSlice_CopyTo(t *testing.T) { + dest := NewDoubleDataPointSlice() + // Test CopyTo to empty + NewDoubleDataPointSlice().CopyTo(dest) + assert.EqualValues(t, NewDoubleDataPointSlice(), dest) + + // Test CopyTo larger slice + generateTestDoubleDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleDataPointSlice(), dest) + + // Test CopyTo same size slice + generateTestDoubleDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleDataPointSlice(), dest) +} + +func TestDoubleDataPointSlice_Resize(t *testing.T) { + es := generateTestDoubleDataPointSlice() + emptyVal := NewDoubleDataPoint() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.DoubleDataPoint]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.DoubleDataPoint]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.DoubleDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.DoubleDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestDoubleDataPointSlice_Append(t *testing.T) { + es := generateTestDoubleDataPointSlice() + + emptyVal := NewDoubleDataPoint() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewDoubleDataPoint() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestDoubleDataPoint_CopyTo(t *testing.T) { + ms := NewDoubleDataPoint() + generateTestDoubleDataPoint().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleDataPoint(), ms) +} + +func TestDoubleDataPoint_LabelsMap(t *testing.T) { + ms := NewDoubleDataPoint() + assert.EqualValues(t, NewStringMap(), ms.LabelsMap()) + fillTestStringMap(ms.LabelsMap()) + testValLabelsMap := generateTestStringMap() + assert.EqualValues(t, testValLabelsMap, ms.LabelsMap()) +} + +func TestDoubleDataPoint_StartTime(t *testing.T) { + ms := NewDoubleDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.StartTime()) + testValStartTime := TimestampUnixNano(1234567890) + ms.SetStartTime(testValStartTime) + assert.EqualValues(t, testValStartTime, ms.StartTime()) +} + +func TestDoubleDataPoint_Timestamp(t *testing.T) { + ms := NewDoubleDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestDoubleDataPoint_Value(t *testing.T) { + ms := NewDoubleDataPoint() + assert.EqualValues(t, float64(0.0), ms.Value()) + testValValue := float64(17.13) + ms.SetValue(testValValue) + assert.EqualValues(t, testValValue, ms.Value()) +} + +func TestDoubleDataPoint_Exemplars(t *testing.T) { + ms := NewDoubleDataPoint() + assert.EqualValues(t, NewDoubleExemplarSlice(), ms.Exemplars()) + fillTestDoubleExemplarSlice(ms.Exemplars()) + testValExemplars := generateTestDoubleExemplarSlice() + assert.EqualValues(t, testValExemplars, ms.Exemplars()) +} + +func TestIntHistogramDataPointSlice(t *testing.T) { + es := NewIntHistogramDataPointSlice() + assert.EqualValues(t, 0, es.Len()) + es = newIntHistogramDataPointSlice(&[]*otlpmetrics.IntHistogramDataPoint{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewIntHistogramDataPoint() + testVal := generateTestIntHistogramDataPoint() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestIntHistogramDataPoint(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestIntHistogramDataPointSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestIntHistogramDataPointSlice() + dest := NewIntHistogramDataPointSlice() + src := generateTestIntHistogramDataPointSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestIntHistogramDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestIntHistogramDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestIntHistogramDataPointSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestIntHistogramDataPointSlice_CopyTo(t *testing.T) { + dest := NewIntHistogramDataPointSlice() + // Test CopyTo to empty + NewIntHistogramDataPointSlice().CopyTo(dest) + assert.EqualValues(t, NewIntHistogramDataPointSlice(), dest) + + // Test CopyTo larger slice + generateTestIntHistogramDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestIntHistogramDataPointSlice(), dest) + + // Test CopyTo same size slice + generateTestIntHistogramDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestIntHistogramDataPointSlice(), dest) +} + +func TestIntHistogramDataPointSlice_Resize(t *testing.T) { + es := generateTestIntHistogramDataPointSlice() + emptyVal := NewIntHistogramDataPoint() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.IntHistogramDataPoint]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.IntHistogramDataPoint]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.IntHistogramDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.IntHistogramDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestIntHistogramDataPointSlice_Append(t *testing.T) { + es := generateTestIntHistogramDataPointSlice() + + emptyVal := NewIntHistogramDataPoint() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewIntHistogramDataPoint() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestIntHistogramDataPoint_CopyTo(t *testing.T) { + ms := NewIntHistogramDataPoint() + generateTestIntHistogramDataPoint().CopyTo(ms) + assert.EqualValues(t, generateTestIntHistogramDataPoint(), ms) +} + +func TestIntHistogramDataPoint_LabelsMap(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, NewStringMap(), ms.LabelsMap()) + fillTestStringMap(ms.LabelsMap()) + testValLabelsMap := generateTestStringMap() + assert.EqualValues(t, testValLabelsMap, ms.LabelsMap()) +} + +func TestIntHistogramDataPoint_StartTime(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.StartTime()) + testValStartTime := TimestampUnixNano(1234567890) + ms.SetStartTime(testValStartTime) + assert.EqualValues(t, testValStartTime, ms.StartTime()) +} + +func TestIntHistogramDataPoint_Timestamp(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestIntHistogramDataPoint_Count(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, uint64(0), ms.Count()) + testValCount := uint64(17) + ms.SetCount(testValCount) + assert.EqualValues(t, testValCount, ms.Count()) +} + +func TestIntHistogramDataPoint_Sum(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, int64(0.0), ms.Sum()) + testValSum := int64(1713) + ms.SetSum(testValSum) + assert.EqualValues(t, testValSum, ms.Sum()) +} + +func TestIntHistogramDataPoint_BucketCounts(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, []uint64(nil), ms.BucketCounts()) + testValBucketCounts := []uint64{1, 2, 3} + ms.SetBucketCounts(testValBucketCounts) + assert.EqualValues(t, testValBucketCounts, ms.BucketCounts()) +} + +func TestIntHistogramDataPoint_ExplicitBounds(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, []float64(nil), ms.ExplicitBounds()) + testValExplicitBounds := []float64{1, 2, 3} + ms.SetExplicitBounds(testValExplicitBounds) + assert.EqualValues(t, testValExplicitBounds, ms.ExplicitBounds()) +} + +func TestIntHistogramDataPoint_Exemplars(t *testing.T) { + ms := NewIntHistogramDataPoint() + assert.EqualValues(t, NewIntExemplarSlice(), ms.Exemplars()) + fillTestIntExemplarSlice(ms.Exemplars()) + testValExemplars := generateTestIntExemplarSlice() + assert.EqualValues(t, testValExemplars, ms.Exemplars()) +} + +func TestDoubleHistogramDataPointSlice(t *testing.T) { + es := NewDoubleHistogramDataPointSlice() + assert.EqualValues(t, 0, es.Len()) + es = newDoubleHistogramDataPointSlice(&[]*otlpmetrics.DoubleHistogramDataPoint{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewDoubleHistogramDataPoint() + testVal := generateTestDoubleHistogramDataPoint() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestDoubleHistogramDataPoint(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestDoubleHistogramDataPointSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestDoubleHistogramDataPointSlice() + dest := NewDoubleHistogramDataPointSlice() + src := generateTestDoubleHistogramDataPointSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleHistogramDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleHistogramDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestDoubleHistogramDataPointSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestDoubleHistogramDataPointSlice_CopyTo(t *testing.T) { + dest := NewDoubleHistogramDataPointSlice() + // Test CopyTo to empty + NewDoubleHistogramDataPointSlice().CopyTo(dest) + assert.EqualValues(t, NewDoubleHistogramDataPointSlice(), dest) + + // Test CopyTo larger slice + generateTestDoubleHistogramDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleHistogramDataPointSlice(), dest) + + // Test CopyTo same size slice + generateTestDoubleHistogramDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleHistogramDataPointSlice(), dest) +} + +func TestDoubleHistogramDataPointSlice_Resize(t *testing.T) { + es := generateTestDoubleHistogramDataPointSlice() + emptyVal := NewDoubleHistogramDataPoint() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.DoubleHistogramDataPoint]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.DoubleHistogramDataPoint]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.DoubleHistogramDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.DoubleHistogramDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestDoubleHistogramDataPointSlice_Append(t *testing.T) { + es := generateTestDoubleHistogramDataPointSlice() + + emptyVal := NewDoubleHistogramDataPoint() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewDoubleHistogramDataPoint() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestDoubleHistogramDataPoint_CopyTo(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + generateTestDoubleHistogramDataPoint().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleHistogramDataPoint(), ms) +} + +func TestDoubleHistogramDataPoint_LabelsMap(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, NewStringMap(), ms.LabelsMap()) + fillTestStringMap(ms.LabelsMap()) + testValLabelsMap := generateTestStringMap() + assert.EqualValues(t, testValLabelsMap, ms.LabelsMap()) +} + +func TestDoubleHistogramDataPoint_StartTime(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.StartTime()) + testValStartTime := TimestampUnixNano(1234567890) + ms.SetStartTime(testValStartTime) + assert.EqualValues(t, testValStartTime, ms.StartTime()) +} + +func TestDoubleHistogramDataPoint_Timestamp(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestDoubleHistogramDataPoint_Count(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, uint64(0), ms.Count()) + testValCount := uint64(17) + ms.SetCount(testValCount) + assert.EqualValues(t, testValCount, ms.Count()) +} + +func TestDoubleHistogramDataPoint_Sum(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, float64(0.0), ms.Sum()) + testValSum := float64(17.13) + ms.SetSum(testValSum) + assert.EqualValues(t, testValSum, ms.Sum()) +} + +func TestDoubleHistogramDataPoint_BucketCounts(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, []uint64(nil), ms.BucketCounts()) + testValBucketCounts := []uint64{1, 2, 3} + ms.SetBucketCounts(testValBucketCounts) + assert.EqualValues(t, testValBucketCounts, ms.BucketCounts()) +} + +func TestDoubleHistogramDataPoint_ExplicitBounds(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, []float64(nil), ms.ExplicitBounds()) + testValExplicitBounds := []float64{1, 2, 3} + ms.SetExplicitBounds(testValExplicitBounds) + assert.EqualValues(t, testValExplicitBounds, ms.ExplicitBounds()) +} + +func TestDoubleHistogramDataPoint_Exemplars(t *testing.T) { + ms := NewDoubleHistogramDataPoint() + assert.EqualValues(t, NewDoubleExemplarSlice(), ms.Exemplars()) + fillTestDoubleExemplarSlice(ms.Exemplars()) + testValExemplars := generateTestDoubleExemplarSlice() + assert.EqualValues(t, testValExemplars, ms.Exemplars()) +} + +func TestDoubleSummaryDataPointSlice(t *testing.T) { + es := NewDoubleSummaryDataPointSlice() + assert.EqualValues(t, 0, es.Len()) + es = newDoubleSummaryDataPointSlice(&[]*otlpmetrics.DoubleSummaryDataPoint{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewDoubleSummaryDataPoint() + testVal := generateTestDoubleSummaryDataPoint() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestDoubleSummaryDataPoint(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestDoubleSummaryDataPointSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestDoubleSummaryDataPointSlice() + dest := NewDoubleSummaryDataPointSlice() + src := generateTestDoubleSummaryDataPointSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleSummaryDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleSummaryDataPointSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestDoubleSummaryDataPointSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestDoubleSummaryDataPointSlice_CopyTo(t *testing.T) { + dest := NewDoubleSummaryDataPointSlice() + // Test CopyTo to empty + NewDoubleSummaryDataPointSlice().CopyTo(dest) + assert.EqualValues(t, NewDoubleSummaryDataPointSlice(), dest) + + // Test CopyTo larger slice + generateTestDoubleSummaryDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleSummaryDataPointSlice(), dest) + + // Test CopyTo same size slice + generateTestDoubleSummaryDataPointSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleSummaryDataPointSlice(), dest) +} + +func TestDoubleSummaryDataPointSlice_Resize(t *testing.T) { + es := generateTestDoubleSummaryDataPointSlice() + emptyVal := NewDoubleSummaryDataPoint() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.DoubleSummaryDataPoint]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.DoubleSummaryDataPoint]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.DoubleSummaryDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.DoubleSummaryDataPoint]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestDoubleSummaryDataPointSlice_Append(t *testing.T) { + es := generateTestDoubleSummaryDataPointSlice() + + emptyVal := NewDoubleSummaryDataPoint() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewDoubleSummaryDataPoint() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestDoubleSummaryDataPoint_CopyTo(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + generateTestDoubleSummaryDataPoint().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleSummaryDataPoint(), ms) +} + +func TestDoubleSummaryDataPoint_LabelsMap(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + assert.EqualValues(t, NewStringMap(), ms.LabelsMap()) + fillTestStringMap(ms.LabelsMap()) + testValLabelsMap := generateTestStringMap() + assert.EqualValues(t, testValLabelsMap, ms.LabelsMap()) +} + +func TestDoubleSummaryDataPoint_StartTime(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.StartTime()) + testValStartTime := TimestampUnixNano(1234567890) + ms.SetStartTime(testValStartTime) + assert.EqualValues(t, testValStartTime, ms.StartTime()) +} + +func TestDoubleSummaryDataPoint_Timestamp(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestDoubleSummaryDataPoint_Count(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + assert.EqualValues(t, uint64(0), ms.Count()) + testValCount := uint64(17) + ms.SetCount(testValCount) + assert.EqualValues(t, testValCount, ms.Count()) +} + +func TestDoubleSummaryDataPoint_Sum(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + assert.EqualValues(t, float64(0.0), ms.Sum()) + testValSum := float64(17.13) + ms.SetSum(testValSum) + assert.EqualValues(t, testValSum, ms.Sum()) +} + +func TestDoubleSummaryDataPoint_QuantileValues(t *testing.T) { + ms := NewDoubleSummaryDataPoint() + assert.EqualValues(t, NewValueAtQuantileSlice(), ms.QuantileValues()) + fillTestValueAtQuantileSlice(ms.QuantileValues()) + testValQuantileValues := generateTestValueAtQuantileSlice() + assert.EqualValues(t, testValQuantileValues, ms.QuantileValues()) +} + +func TestValueAtQuantileSlice(t *testing.T) { + es := NewValueAtQuantileSlice() + assert.EqualValues(t, 0, es.Len()) + es = newValueAtQuantileSlice(&[]*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewValueAtQuantile() + testVal := generateTestValueAtQuantile() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestValueAtQuantile(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestValueAtQuantileSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestValueAtQuantileSlice() + dest := NewValueAtQuantileSlice() + src := generateTestValueAtQuantileSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestValueAtQuantileSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestValueAtQuantileSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestValueAtQuantileSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestValueAtQuantileSlice_CopyTo(t *testing.T) { + dest := NewValueAtQuantileSlice() + // Test CopyTo to empty + NewValueAtQuantileSlice().CopyTo(dest) + assert.EqualValues(t, NewValueAtQuantileSlice(), dest) + + // Test CopyTo larger slice + generateTestValueAtQuantileSlice().CopyTo(dest) + assert.EqualValues(t, generateTestValueAtQuantileSlice(), dest) + + // Test CopyTo same size slice + generateTestValueAtQuantileSlice().CopyTo(dest) + assert.EqualValues(t, generateTestValueAtQuantileSlice(), dest) +} + +func TestValueAtQuantileSlice_Resize(t *testing.T) { + es := generateTestValueAtQuantileSlice() + emptyVal := NewValueAtQuantile() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestValueAtQuantileSlice_Append(t *testing.T) { + es := generateTestValueAtQuantileSlice() + + emptyVal := NewValueAtQuantile() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewValueAtQuantile() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestValueAtQuantile_CopyTo(t *testing.T) { + ms := NewValueAtQuantile() + generateTestValueAtQuantile().CopyTo(ms) + assert.EqualValues(t, generateTestValueAtQuantile(), ms) +} + +func TestValueAtQuantile_Quantile(t *testing.T) { + ms := NewValueAtQuantile() + assert.EqualValues(t, float64(0.0), ms.Quantile()) + testValQuantile := float64(17.13) + ms.SetQuantile(testValQuantile) + assert.EqualValues(t, testValQuantile, ms.Quantile()) +} + +func TestValueAtQuantile_Value(t *testing.T) { + ms := NewValueAtQuantile() + assert.EqualValues(t, float64(0.0), ms.Value()) + testValValue := float64(17.13) + ms.SetValue(testValValue) + assert.EqualValues(t, testValValue, ms.Value()) +} + +func TestIntExemplarSlice(t *testing.T) { + es := NewIntExemplarSlice() + assert.EqualValues(t, 0, es.Len()) + es = newIntExemplarSlice(&[]*otlpmetrics.IntExemplar{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewIntExemplar() + testVal := generateTestIntExemplar() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestIntExemplar(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestIntExemplarSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestIntExemplarSlice() + dest := NewIntExemplarSlice() + src := generateTestIntExemplarSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestIntExemplarSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestIntExemplarSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestIntExemplarSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestIntExemplarSlice_CopyTo(t *testing.T) { + dest := NewIntExemplarSlice() + // Test CopyTo to empty + NewIntExemplarSlice().CopyTo(dest) + assert.EqualValues(t, NewIntExemplarSlice(), dest) + + // Test CopyTo larger slice + generateTestIntExemplarSlice().CopyTo(dest) + assert.EqualValues(t, generateTestIntExemplarSlice(), dest) + + // Test CopyTo same size slice + generateTestIntExemplarSlice().CopyTo(dest) + assert.EqualValues(t, generateTestIntExemplarSlice(), dest) +} + +func TestIntExemplarSlice_Resize(t *testing.T) { + es := generateTestIntExemplarSlice() + emptyVal := NewIntExemplar() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.IntExemplar]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.IntExemplar]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.IntExemplar]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.IntExemplar]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestIntExemplarSlice_Append(t *testing.T) { + es := generateTestIntExemplarSlice() + + emptyVal := NewIntExemplar() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewIntExemplar() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestIntExemplar_CopyTo(t *testing.T) { + ms := NewIntExemplar() + generateTestIntExemplar().CopyTo(ms) + assert.EqualValues(t, generateTestIntExemplar(), ms) +} + +func TestIntExemplar_Timestamp(t *testing.T) { + ms := NewIntExemplar() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestIntExemplar_Value(t *testing.T) { + ms := NewIntExemplar() + assert.EqualValues(t, int64(0), ms.Value()) + testValValue := int64(-17) + ms.SetValue(testValValue) + assert.EqualValues(t, testValValue, ms.Value()) +} + +func TestIntExemplar_FilteredLabels(t *testing.T) { + ms := NewIntExemplar() + assert.EqualValues(t, NewStringMap(), ms.FilteredLabels()) + fillTestStringMap(ms.FilteredLabels()) + testValFilteredLabels := generateTestStringMap() + assert.EqualValues(t, testValFilteredLabels, ms.FilteredLabels()) +} + +func TestDoubleExemplarSlice(t *testing.T) { + es := NewDoubleExemplarSlice() + assert.EqualValues(t, 0, es.Len()) + es = newDoubleExemplarSlice(&[]*otlpmetrics.DoubleExemplar{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewDoubleExemplar() + testVal := generateTestDoubleExemplar() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestDoubleExemplar(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestDoubleExemplarSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestDoubleExemplarSlice() + dest := NewDoubleExemplarSlice() + src := generateTestDoubleExemplarSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleExemplarSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestDoubleExemplarSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestDoubleExemplarSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestDoubleExemplarSlice_CopyTo(t *testing.T) { + dest := NewDoubleExemplarSlice() + // Test CopyTo to empty + NewDoubleExemplarSlice().CopyTo(dest) + assert.EqualValues(t, NewDoubleExemplarSlice(), dest) + + // Test CopyTo larger slice + generateTestDoubleExemplarSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleExemplarSlice(), dest) + + // Test CopyTo same size slice + generateTestDoubleExemplarSlice().CopyTo(dest) + assert.EqualValues(t, generateTestDoubleExemplarSlice(), dest) +} + +func TestDoubleExemplarSlice_Resize(t *testing.T) { + es := generateTestDoubleExemplarSlice() + emptyVal := NewDoubleExemplar() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlpmetrics.DoubleExemplar]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlpmetrics.DoubleExemplar]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlpmetrics.DoubleExemplar]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlpmetrics.DoubleExemplar]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestDoubleExemplarSlice_Append(t *testing.T) { + es := generateTestDoubleExemplarSlice() + + emptyVal := NewDoubleExemplar() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewDoubleExemplar() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestDoubleExemplar_CopyTo(t *testing.T) { + ms := NewDoubleExemplar() + generateTestDoubleExemplar().CopyTo(ms) + assert.EqualValues(t, generateTestDoubleExemplar(), ms) +} + +func TestDoubleExemplar_Timestamp(t *testing.T) { + ms := NewDoubleExemplar() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestDoubleExemplar_Value(t *testing.T) { + ms := NewDoubleExemplar() + assert.EqualValues(t, float64(0.0), ms.Value()) + testValValue := float64(17.13) + ms.SetValue(testValValue) + assert.EqualValues(t, testValValue, ms.Value()) +} + +func TestDoubleExemplar_FilteredLabels(t *testing.T) { + ms := NewDoubleExemplar() + assert.EqualValues(t, NewStringMap(), ms.FilteredLabels()) + fillTestStringMap(ms.FilteredLabels()) + testValFilteredLabels := generateTestStringMap() + assert.EqualValues(t, testValFilteredLabels, ms.FilteredLabels()) +} + +func generateTestResourceMetricsSlice() ResourceMetricsSlice { + tv := NewResourceMetricsSlice() + fillTestResourceMetricsSlice(tv) + return tv +} + +func fillTestResourceMetricsSlice(tv ResourceMetricsSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestResourceMetrics(tv.At(i)) + } +} + +func generateTestResourceMetrics() ResourceMetrics { + tv := NewResourceMetrics() + fillTestResourceMetrics(tv) + return tv +} + +func fillTestResourceMetrics(tv ResourceMetrics) { + fillTestResource(tv.Resource()) + fillTestInstrumentationLibraryMetricsSlice(tv.InstrumentationLibraryMetrics()) +} + +func generateTestInstrumentationLibraryMetricsSlice() InstrumentationLibraryMetricsSlice { + tv := NewInstrumentationLibraryMetricsSlice() + fillTestInstrumentationLibraryMetricsSlice(tv) + return tv +} + +func fillTestInstrumentationLibraryMetricsSlice(tv InstrumentationLibraryMetricsSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestInstrumentationLibraryMetrics(tv.At(i)) + } +} + +func generateTestInstrumentationLibraryMetrics() InstrumentationLibraryMetrics { + tv := NewInstrumentationLibraryMetrics() + fillTestInstrumentationLibraryMetrics(tv) + return tv +} + +func fillTestInstrumentationLibraryMetrics(tv InstrumentationLibraryMetrics) { + fillTestInstrumentationLibrary(tv.InstrumentationLibrary()) + fillTestMetricSlice(tv.Metrics()) +} + +func generateTestMetricSlice() MetricSlice { + tv := NewMetricSlice() + fillTestMetricSlice(tv) + return tv +} + +func fillTestMetricSlice(tv MetricSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestMetric(tv.At(i)) + } +} + +func generateTestMetric() Metric { + tv := NewMetric() + fillTestMetric(tv) + return tv +} + +func fillTestMetric(tv Metric) { + tv.SetName("test_name") + tv.SetDescription("test_description") + tv.SetUnit("1") + (*tv.orig).Data = &otlpmetrics.Metric_IntGauge{IntGauge: &otlpmetrics.IntGauge{}} + fillTestIntGauge(tv.IntGauge()) +} + +func generateTestIntGauge() IntGauge { + tv := NewIntGauge() + fillTestIntGauge(tv) + return tv +} + +func fillTestIntGauge(tv IntGauge) { + fillTestIntDataPointSlice(tv.DataPoints()) +} + +func generateTestDoubleGauge() DoubleGauge { + tv := NewDoubleGauge() + fillTestDoubleGauge(tv) + return tv +} + +func fillTestDoubleGauge(tv DoubleGauge) { + fillTestDoubleDataPointSlice(tv.DataPoints()) +} + +func generateTestIntSum() IntSum { + tv := NewIntSum() + fillTestIntSum(tv) + return tv +} + +func fillTestIntSum(tv IntSum) { + tv.SetAggregationTemporality(AggregationTemporalityCumulative) + tv.SetIsMonotonic(true) + fillTestIntDataPointSlice(tv.DataPoints()) +} + +func generateTestDoubleSum() DoubleSum { + tv := NewDoubleSum() + fillTestDoubleSum(tv) + return tv +} + +func fillTestDoubleSum(tv DoubleSum) { + tv.SetAggregationTemporality(AggregationTemporalityCumulative) + tv.SetIsMonotonic(true) + fillTestDoubleDataPointSlice(tv.DataPoints()) +} + +func generateTestIntHistogram() IntHistogram { + tv := NewIntHistogram() + fillTestIntHistogram(tv) + return tv +} + +func fillTestIntHistogram(tv IntHistogram) { + tv.SetAggregationTemporality(AggregationTemporalityCumulative) + fillTestIntHistogramDataPointSlice(tv.DataPoints()) +} + +func generateTestDoubleHistogram() DoubleHistogram { + tv := NewDoubleHistogram() + fillTestDoubleHistogram(tv) + return tv +} + +func fillTestDoubleHistogram(tv DoubleHistogram) { + tv.SetAggregationTemporality(AggregationTemporalityCumulative) + fillTestDoubleHistogramDataPointSlice(tv.DataPoints()) +} + +func generateTestDoubleSummary() DoubleSummary { + tv := NewDoubleSummary() + fillTestDoubleSummary(tv) + return tv +} + +func fillTestDoubleSummary(tv DoubleSummary) { + fillTestDoubleSummaryDataPointSlice(tv.DataPoints()) +} + +func generateTestIntDataPointSlice() IntDataPointSlice { + tv := NewIntDataPointSlice() + fillTestIntDataPointSlice(tv) + return tv +} + +func fillTestIntDataPointSlice(tv IntDataPointSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestIntDataPoint(tv.At(i)) + } +} + +func generateTestIntDataPoint() IntDataPoint { + tv := NewIntDataPoint() + fillTestIntDataPoint(tv) + return tv +} + +func fillTestIntDataPoint(tv IntDataPoint) { + fillTestStringMap(tv.LabelsMap()) + tv.SetStartTime(TimestampUnixNano(1234567890)) + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetValue(int64(-17)) + fillTestIntExemplarSlice(tv.Exemplars()) +} + +func generateTestDoubleDataPointSlice() DoubleDataPointSlice { + tv := NewDoubleDataPointSlice() + fillTestDoubleDataPointSlice(tv) + return tv +} + +func fillTestDoubleDataPointSlice(tv DoubleDataPointSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestDoubleDataPoint(tv.At(i)) + } +} + +func generateTestDoubleDataPoint() DoubleDataPoint { + tv := NewDoubleDataPoint() + fillTestDoubleDataPoint(tv) + return tv +} + +func fillTestDoubleDataPoint(tv DoubleDataPoint) { + fillTestStringMap(tv.LabelsMap()) + tv.SetStartTime(TimestampUnixNano(1234567890)) + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetValue(float64(17.13)) + fillTestDoubleExemplarSlice(tv.Exemplars()) +} + +func generateTestIntHistogramDataPointSlice() IntHistogramDataPointSlice { + tv := NewIntHistogramDataPointSlice() + fillTestIntHistogramDataPointSlice(tv) + return tv +} + +func fillTestIntHistogramDataPointSlice(tv IntHistogramDataPointSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestIntHistogramDataPoint(tv.At(i)) + } +} + +func generateTestIntHistogramDataPoint() IntHistogramDataPoint { + tv := NewIntHistogramDataPoint() + fillTestIntHistogramDataPoint(tv) + return tv +} + +func fillTestIntHistogramDataPoint(tv IntHistogramDataPoint) { + fillTestStringMap(tv.LabelsMap()) + tv.SetStartTime(TimestampUnixNano(1234567890)) + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetCount(uint64(17)) + tv.SetSum(int64(1713)) + tv.SetBucketCounts([]uint64{1, 2, 3}) + tv.SetExplicitBounds([]float64{1, 2, 3}) + fillTestIntExemplarSlice(tv.Exemplars()) +} + +func generateTestDoubleHistogramDataPointSlice() DoubleHistogramDataPointSlice { + tv := NewDoubleHistogramDataPointSlice() + fillTestDoubleHistogramDataPointSlice(tv) + return tv +} + +func fillTestDoubleHistogramDataPointSlice(tv DoubleHistogramDataPointSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestDoubleHistogramDataPoint(tv.At(i)) + } +} + +func generateTestDoubleHistogramDataPoint() DoubleHistogramDataPoint { + tv := NewDoubleHistogramDataPoint() + fillTestDoubleHistogramDataPoint(tv) + return tv +} + +func fillTestDoubleHistogramDataPoint(tv DoubleHistogramDataPoint) { + fillTestStringMap(tv.LabelsMap()) + tv.SetStartTime(TimestampUnixNano(1234567890)) + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetCount(uint64(17)) + tv.SetSum(float64(17.13)) + tv.SetBucketCounts([]uint64{1, 2, 3}) + tv.SetExplicitBounds([]float64{1, 2, 3}) + fillTestDoubleExemplarSlice(tv.Exemplars()) +} + +func generateTestDoubleSummaryDataPointSlice() DoubleSummaryDataPointSlice { + tv := NewDoubleSummaryDataPointSlice() + fillTestDoubleSummaryDataPointSlice(tv) + return tv +} + +func fillTestDoubleSummaryDataPointSlice(tv DoubleSummaryDataPointSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestDoubleSummaryDataPoint(tv.At(i)) + } +} + +func generateTestDoubleSummaryDataPoint() DoubleSummaryDataPoint { + tv := NewDoubleSummaryDataPoint() + fillTestDoubleSummaryDataPoint(tv) + return tv +} + +func fillTestDoubleSummaryDataPoint(tv DoubleSummaryDataPoint) { + fillTestStringMap(tv.LabelsMap()) + tv.SetStartTime(TimestampUnixNano(1234567890)) + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetCount(uint64(17)) + tv.SetSum(float64(17.13)) + fillTestValueAtQuantileSlice(tv.QuantileValues()) +} + +func generateTestValueAtQuantileSlice() ValueAtQuantileSlice { + tv := NewValueAtQuantileSlice() + fillTestValueAtQuantileSlice(tv) + return tv +} + +func fillTestValueAtQuantileSlice(tv ValueAtQuantileSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestValueAtQuantile(tv.At(i)) + } +} + +func generateTestValueAtQuantile() ValueAtQuantile { + tv := NewValueAtQuantile() + fillTestValueAtQuantile(tv) + return tv +} + +func fillTestValueAtQuantile(tv ValueAtQuantile) { + tv.SetQuantile(float64(17.13)) + tv.SetValue(float64(17.13)) +} + +func generateTestIntExemplarSlice() IntExemplarSlice { + tv := NewIntExemplarSlice() + fillTestIntExemplarSlice(tv) + return tv +} + +func fillTestIntExemplarSlice(tv IntExemplarSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestIntExemplar(tv.At(i)) + } +} + +func generateTestIntExemplar() IntExemplar { + tv := NewIntExemplar() + fillTestIntExemplar(tv) + return tv +} + +func fillTestIntExemplar(tv IntExemplar) { + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetValue(int64(-17)) + fillTestStringMap(tv.FilteredLabels()) +} + +func generateTestDoubleExemplarSlice() DoubleExemplarSlice { + tv := NewDoubleExemplarSlice() + fillTestDoubleExemplarSlice(tv) + return tv +} + +func fillTestDoubleExemplarSlice(tv DoubleExemplarSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestDoubleExemplar(tv.At(i)) + } +} + +func generateTestDoubleExemplar() DoubleExemplar { + tv := NewDoubleExemplar() + fillTestDoubleExemplar(tv) + return tv +} + +func fillTestDoubleExemplar(tv DoubleExemplar) { + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetValue(float64(17.13)) + fillTestStringMap(tv.FilteredLabels()) +} diff --git a/internal/otel_collector/consumer/pdata/generated_resource.go b/internal/otel_collector/consumer/pdata/generated_resource.go new file mode 100644 index 00000000000..d50abb781e6 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_resource.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" +) + +// Resource information. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResource function to create new instances. +// Important: zero-initialized instance is not valid for use. +type Resource struct { + orig *otlpresource.Resource +} + +func newResource(orig *otlpresource.Resource) Resource { + return Resource{orig: orig} +} + +// NewResource creates a new empty Resource. +// +// This must be used only in testing code since no "Set" method available. +func NewResource() Resource { + return newResource(&otlpresource.Resource{}) +} + +// Attributes returns the Attributes associated with this Resource. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Resource) Attributes() AttributeMap { + return newAttributeMap(&(*ms.orig).Attributes) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms Resource) CopyTo(dest Resource) { + ms.Attributes().CopyTo(dest.Attributes()) +} diff --git a/internal/otel_collector/consumer/pdata/generated_resource_test.go b/internal/otel_collector/consumer/pdata/generated_resource_test.go new file mode 100644 index 00000000000..cff3992e35f --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_resource_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResource_CopyTo(t *testing.T) { + ms := NewResource() + generateTestResource().CopyTo(ms) + assert.EqualValues(t, generateTestResource(), ms) +} + +func TestResource_Attributes(t *testing.T) { + ms := NewResource() + assert.EqualValues(t, NewAttributeMap(), ms.Attributes()) + fillTestAttributeMap(ms.Attributes()) + testValAttributes := generateTestAttributeMap() + assert.EqualValues(t, testValAttributes, ms.Attributes()) +} + +func generateTestResource() Resource { + tv := NewResource() + fillTestResource(tv) + return tv +} + +func fillTestResource(tv Resource) { + fillTestAttributeMap(tv.Attributes()) +} diff --git a/internal/otel_collector/consumer/pdata/generated_trace.go b/internal/otel_collector/consumer/pdata/generated_trace.go new file mode 100644 index 00000000000..2158d3ebf79 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_trace.go @@ -0,0 +1,1141 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "go.opentelemetry.io/collector/internal/data" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +// ResourceSpansSlice logically represents a slice of ResourceSpans. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResourceSpansSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ResourceSpansSlice struct { + // orig points to the slice otlptrace.ResourceSpans field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlptrace.ResourceSpans +} + +func newResourceSpansSlice(orig *[]*otlptrace.ResourceSpans) ResourceSpansSlice { + return ResourceSpansSlice{orig} +} + +// NewResourceSpansSlice creates a ResourceSpansSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewResourceSpansSlice() ResourceSpansSlice { + orig := []*otlptrace.ResourceSpans(nil) + return ResourceSpansSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewResourceSpansSlice()". +func (es ResourceSpansSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es ResourceSpansSlice) At(ix int) ResourceSpans { + return newResourceSpans((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es ResourceSpansSlice) MoveAndAppendTo(dest ResourceSpansSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es ResourceSpansSlice) CopyTo(dest ResourceSpansSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newResourceSpans((*es.orig)[i]).CopyTo(newResourceSpans((*dest.orig)[i])) + } + return + } + origs := make([]otlptrace.ResourceSpans, srcLen) + wrappers := make([]*otlptrace.ResourceSpans, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newResourceSpans((*es.orig)[i]).CopyTo(newResourceSpans(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new ResourceSpansSlice can be initialized: +// es := NewResourceSpansSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es ResourceSpansSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlptrace.ResourceSpans, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlptrace.ResourceSpans, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the ResourceSpansSlice by one and set the +// given ResourceSpans at that new position. The original ResourceSpans +// could still be referenced so do not reuse it after passing it to this +// method. +func (es ResourceSpansSlice) Append(e ResourceSpans) { + *es.orig = append(*es.orig, e.orig) +} + +// InstrumentationLibrarySpans is a collection of spans from a LibraryInstrumentation. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewResourceSpans function to create new instances. +// Important: zero-initialized instance is not valid for use. +type ResourceSpans struct { + orig *otlptrace.ResourceSpans +} + +func newResourceSpans(orig *otlptrace.ResourceSpans) ResourceSpans { + return ResourceSpans{orig: orig} +} + +// NewResourceSpans creates a new empty ResourceSpans. +// +// This must be used only in testing code since no "Set" method available. +func NewResourceSpans() ResourceSpans { + return newResourceSpans(&otlptrace.ResourceSpans{}) +} + +// Resource returns the resource associated with this ResourceSpans. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ResourceSpans) Resource() Resource { + return newResource(&(*ms.orig).Resource) +} + +// InstrumentationLibrarySpans returns the InstrumentationLibrarySpans associated with this ResourceSpans. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms ResourceSpans) InstrumentationLibrarySpans() InstrumentationLibrarySpansSlice { + return newInstrumentationLibrarySpansSlice(&(*ms.orig).InstrumentationLibrarySpans) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms ResourceSpans) CopyTo(dest ResourceSpans) { + ms.Resource().CopyTo(dest.Resource()) + ms.InstrumentationLibrarySpans().CopyTo(dest.InstrumentationLibrarySpans()) +} + +// InstrumentationLibrarySpansSlice logically represents a slice of InstrumentationLibrarySpans. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibrarySpansSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibrarySpansSlice struct { + // orig points to the slice otlptrace.InstrumentationLibrarySpans field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlptrace.InstrumentationLibrarySpans +} + +func newInstrumentationLibrarySpansSlice(orig *[]*otlptrace.InstrumentationLibrarySpans) InstrumentationLibrarySpansSlice { + return InstrumentationLibrarySpansSlice{orig} +} + +// NewInstrumentationLibrarySpansSlice creates a InstrumentationLibrarySpansSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewInstrumentationLibrarySpansSlice() InstrumentationLibrarySpansSlice { + orig := []*otlptrace.InstrumentationLibrarySpans(nil) + return InstrumentationLibrarySpansSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewInstrumentationLibrarySpansSlice()". +func (es InstrumentationLibrarySpansSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es InstrumentationLibrarySpansSlice) At(ix int) InstrumentationLibrarySpans { + return newInstrumentationLibrarySpans((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es InstrumentationLibrarySpansSlice) MoveAndAppendTo(dest InstrumentationLibrarySpansSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es InstrumentationLibrarySpansSlice) CopyTo(dest InstrumentationLibrarySpansSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newInstrumentationLibrarySpans((*es.orig)[i]).CopyTo(newInstrumentationLibrarySpans((*dest.orig)[i])) + } + return + } + origs := make([]otlptrace.InstrumentationLibrarySpans, srcLen) + wrappers := make([]*otlptrace.InstrumentationLibrarySpans, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newInstrumentationLibrarySpans((*es.orig)[i]).CopyTo(newInstrumentationLibrarySpans(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new InstrumentationLibrarySpansSlice can be initialized: +// es := NewInstrumentationLibrarySpansSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es InstrumentationLibrarySpansSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlptrace.InstrumentationLibrarySpans, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlptrace.InstrumentationLibrarySpans, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the InstrumentationLibrarySpansSlice by one and set the +// given InstrumentationLibrarySpans at that new position. The original InstrumentationLibrarySpans +// could still be referenced so do not reuse it after passing it to this +// method. +func (es InstrumentationLibrarySpansSlice) Append(e InstrumentationLibrarySpans) { + *es.orig = append(*es.orig, e.orig) +} + +// InstrumentationLibrarySpans is a collection of spans from a LibraryInstrumentation. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewInstrumentationLibrarySpans function to create new instances. +// Important: zero-initialized instance is not valid for use. +type InstrumentationLibrarySpans struct { + orig *otlptrace.InstrumentationLibrarySpans +} + +func newInstrumentationLibrarySpans(orig *otlptrace.InstrumentationLibrarySpans) InstrumentationLibrarySpans { + return InstrumentationLibrarySpans{orig: orig} +} + +// NewInstrumentationLibrarySpans creates a new empty InstrumentationLibrarySpans. +// +// This must be used only in testing code since no "Set" method available. +func NewInstrumentationLibrarySpans() InstrumentationLibrarySpans { + return newInstrumentationLibrarySpans(&otlptrace.InstrumentationLibrarySpans{}) +} + +// InstrumentationLibrary returns the instrumentationlibrary associated with this InstrumentationLibrarySpans. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibrarySpans) InstrumentationLibrary() InstrumentationLibrary { + return newInstrumentationLibrary(&(*ms.orig).InstrumentationLibrary) +} + +// Spans returns the Spans associated with this InstrumentationLibrarySpans. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms InstrumentationLibrarySpans) Spans() SpanSlice { + return newSpanSlice(&(*ms.orig).Spans) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms InstrumentationLibrarySpans) CopyTo(dest InstrumentationLibrarySpans) { + ms.InstrumentationLibrary().CopyTo(dest.InstrumentationLibrary()) + ms.Spans().CopyTo(dest.Spans()) +} + +// SpanSlice logically represents a slice of Span. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpanSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type SpanSlice struct { + // orig points to the slice otlptrace.Span field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlptrace.Span +} + +func newSpanSlice(orig *[]*otlptrace.Span) SpanSlice { + return SpanSlice{orig} +} + +// NewSpanSlice creates a SpanSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewSpanSlice() SpanSlice { + orig := []*otlptrace.Span(nil) + return SpanSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewSpanSlice()". +func (es SpanSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es SpanSlice) At(ix int) Span { + return newSpan((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es SpanSlice) MoveAndAppendTo(dest SpanSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es SpanSlice) CopyTo(dest SpanSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newSpan((*es.orig)[i]).CopyTo(newSpan((*dest.orig)[i])) + } + return + } + origs := make([]otlptrace.Span, srcLen) + wrappers := make([]*otlptrace.Span, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newSpan((*es.orig)[i]).CopyTo(newSpan(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new SpanSlice can be initialized: +// es := NewSpanSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es SpanSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlptrace.Span, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlptrace.Span, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the SpanSlice by one and set the +// given Span at that new position. The original Span +// could still be referenced so do not reuse it after passing it to this +// method. +func (es SpanSlice) Append(e Span) { + *es.orig = append(*es.orig, e.orig) +} + +// Span represents a single operation within a trace. +// See Span definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/trace/v1/trace.proto#L37 +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpan function to create new instances. +// Important: zero-initialized instance is not valid for use. +type Span struct { + orig *otlptrace.Span +} + +func newSpan(orig *otlptrace.Span) Span { + return Span{orig: orig} +} + +// NewSpan creates a new empty Span. +// +// This must be used only in testing code since no "Set" method available. +func NewSpan() Span { + return newSpan(&otlptrace.Span{}) +} + +// TraceID returns the traceid associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) TraceID() TraceID { + return TraceID((*ms.orig).TraceId) +} + +// SetTraceID replaces the traceid associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetTraceID(v TraceID) { + (*ms.orig).TraceId = data.TraceID(v) +} + +// SpanID returns the spanid associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SpanID() SpanID { + return SpanID((*ms.orig).SpanId) +} + +// SetSpanID replaces the spanid associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetSpanID(v SpanID) { + (*ms.orig).SpanId = data.SpanID(v) +} + +// TraceState returns the tracestate associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) TraceState() TraceState { + return TraceState((*ms.orig).TraceState) +} + +// SetTraceState replaces the tracestate associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetTraceState(v TraceState) { + (*ms.orig).TraceState = string(v) +} + +// ParentSpanID returns the parentspanid associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) ParentSpanID() SpanID { + return SpanID((*ms.orig).ParentSpanId) +} + +// SetParentSpanID replaces the parentspanid associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetParentSpanID(v SpanID) { + (*ms.orig).ParentSpanId = data.SpanID(v) +} + +// Name returns the name associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) Name() string { + return (*ms.orig).Name +} + +// SetName replaces the name associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetName(v string) { + (*ms.orig).Name = v +} + +// Kind returns the kind associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) Kind() SpanKind { + return SpanKind((*ms.orig).Kind) +} + +// SetKind replaces the kind associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetKind(v SpanKind) { + (*ms.orig).Kind = otlptrace.Span_SpanKind(v) +} + +// StartTime returns the starttime associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) StartTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).StartTimeUnixNano) +} + +// SetStartTime replaces the starttime associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetStartTime(v TimestampUnixNano) { + (*ms.orig).StartTimeUnixNano = uint64(v) +} + +// EndTime returns the endtime associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) EndTime() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).EndTimeUnixNano) +} + +// SetEndTime replaces the endtime associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetEndTime(v TimestampUnixNano) { + (*ms.orig).EndTimeUnixNano = uint64(v) +} + +// Attributes returns the Attributes associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) Attributes() AttributeMap { + return newAttributeMap(&(*ms.orig).Attributes) +} + +// DroppedAttributesCount returns the droppedattributescount associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) DroppedAttributesCount() uint32 { + return (*ms.orig).DroppedAttributesCount +} + +// SetDroppedAttributesCount replaces the droppedattributescount associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetDroppedAttributesCount(v uint32) { + (*ms.orig).DroppedAttributesCount = v +} + +// Events returns the Events associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) Events() SpanEventSlice { + return newSpanEventSlice(&(*ms.orig).Events) +} + +// DroppedEventsCount returns the droppedeventscount associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) DroppedEventsCount() uint32 { + return (*ms.orig).DroppedEventsCount +} + +// SetDroppedEventsCount replaces the droppedeventscount associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetDroppedEventsCount(v uint32) { + (*ms.orig).DroppedEventsCount = v +} + +// Links returns the Links associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) Links() SpanLinkSlice { + return newSpanLinkSlice(&(*ms.orig).Links) +} + +// DroppedLinksCount returns the droppedlinkscount associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) DroppedLinksCount() uint32 { + return (*ms.orig).DroppedLinksCount +} + +// SetDroppedLinksCount replaces the droppedlinkscount associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) SetDroppedLinksCount(v uint32) { + (*ms.orig).DroppedLinksCount = v +} + +// Status returns the status associated with this Span. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms Span) Status() SpanStatus { + return newSpanStatus(&(*ms.orig).Status) +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms Span) CopyTo(dest Span) { + dest.SetTraceID(ms.TraceID()) + dest.SetSpanID(ms.SpanID()) + dest.SetTraceState(ms.TraceState()) + dest.SetParentSpanID(ms.ParentSpanID()) + dest.SetName(ms.Name()) + dest.SetKind(ms.Kind()) + dest.SetStartTime(ms.StartTime()) + dest.SetEndTime(ms.EndTime()) + ms.Attributes().CopyTo(dest.Attributes()) + dest.SetDroppedAttributesCount(ms.DroppedAttributesCount()) + ms.Events().CopyTo(dest.Events()) + dest.SetDroppedEventsCount(ms.DroppedEventsCount()) + ms.Links().CopyTo(dest.Links()) + dest.SetDroppedLinksCount(ms.DroppedLinksCount()) + ms.Status().CopyTo(dest.Status()) +} + +// SpanEventSlice logically represents a slice of SpanEvent. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpanEventSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type SpanEventSlice struct { + // orig points to the slice otlptrace.Span_Event field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlptrace.Span_Event +} + +func newSpanEventSlice(orig *[]*otlptrace.Span_Event) SpanEventSlice { + return SpanEventSlice{orig} +} + +// NewSpanEventSlice creates a SpanEventSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewSpanEventSlice() SpanEventSlice { + orig := []*otlptrace.Span_Event(nil) + return SpanEventSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewSpanEventSlice()". +func (es SpanEventSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es SpanEventSlice) At(ix int) SpanEvent { + return newSpanEvent((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es SpanEventSlice) MoveAndAppendTo(dest SpanEventSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es SpanEventSlice) CopyTo(dest SpanEventSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newSpanEvent((*es.orig)[i]).CopyTo(newSpanEvent((*dest.orig)[i])) + } + return + } + origs := make([]otlptrace.Span_Event, srcLen) + wrappers := make([]*otlptrace.Span_Event, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newSpanEvent((*es.orig)[i]).CopyTo(newSpanEvent(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new SpanEventSlice can be initialized: +// es := NewSpanEventSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es SpanEventSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlptrace.Span_Event, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlptrace.Span_Event, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the SpanEventSlice by one and set the +// given SpanEvent at that new position. The original SpanEvent +// could still be referenced so do not reuse it after passing it to this +// method. +func (es SpanEventSlice) Append(e SpanEvent) { + *es.orig = append(*es.orig, e.orig) +} + +// SpanEvent is a time-stamped annotation of the span, consisting of user-supplied +// text description and key-value pairs. See OTLP for event definition. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpanEvent function to create new instances. +// Important: zero-initialized instance is not valid for use. +type SpanEvent struct { + orig *otlptrace.Span_Event +} + +func newSpanEvent(orig *otlptrace.Span_Event) SpanEvent { + return SpanEvent{orig: orig} +} + +// NewSpanEvent creates a new empty SpanEvent. +// +// This must be used only in testing code since no "Set" method available. +func NewSpanEvent() SpanEvent { + return newSpanEvent(&otlptrace.Span_Event{}) +} + +// Timestamp returns the timestamp associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) Timestamp() TimestampUnixNano { + return TimestampUnixNano((*ms.orig).TimeUnixNano) +} + +// SetTimestamp replaces the timestamp associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) SetTimestamp(v TimestampUnixNano) { + (*ms.orig).TimeUnixNano = uint64(v) +} + +// Name returns the name associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) Name() string { + return (*ms.orig).Name +} + +// SetName replaces the name associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) SetName(v string) { + (*ms.orig).Name = v +} + +// Attributes returns the Attributes associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) Attributes() AttributeMap { + return newAttributeMap(&(*ms.orig).Attributes) +} + +// DroppedAttributesCount returns the droppedattributescount associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) DroppedAttributesCount() uint32 { + return (*ms.orig).DroppedAttributesCount +} + +// SetDroppedAttributesCount replaces the droppedattributescount associated with this SpanEvent. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanEvent) SetDroppedAttributesCount(v uint32) { + (*ms.orig).DroppedAttributesCount = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms SpanEvent) CopyTo(dest SpanEvent) { + dest.SetTimestamp(ms.Timestamp()) + dest.SetName(ms.Name()) + ms.Attributes().CopyTo(dest.Attributes()) + dest.SetDroppedAttributesCount(ms.DroppedAttributesCount()) +} + +// SpanLinkSlice logically represents a slice of SpanLink. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpanLinkSlice function to create new instances. +// Important: zero-initialized instance is not valid for use. +type SpanLinkSlice struct { + // orig points to the slice otlptrace.Span_Link field contained somewhere else. + // We use pointer-to-slice to be able to modify it in functions like Resize. + orig *[]*otlptrace.Span_Link +} + +func newSpanLinkSlice(orig *[]*otlptrace.Span_Link) SpanLinkSlice { + return SpanLinkSlice{orig} +} + +// NewSpanLinkSlice creates a SpanLinkSlice with 0 elements. +// Can use "Resize" to initialize with a given length. +func NewSpanLinkSlice() SpanLinkSlice { + orig := []*otlptrace.Span_Link(nil) + return SpanLinkSlice{&orig} +} + +// Len returns the number of elements in the slice. +// +// Returns "0" for a newly instance created with "NewSpanLinkSlice()". +func (es SpanLinkSlice) Len() int { + return len(*es.orig) +} + +// At returns the element at the given index. +// +// This function is used mostly for iterating over all the values in the slice: +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// ... // Do something with the element +// } +func (es SpanLinkSlice) At(ix int) SpanLink { + return newSpanLink((*es.orig)[ix]) +} + +// MoveAndAppendTo moves all elements from the current slice and appends them to the dest. +// The current slice will be cleared. +func (es SpanLinkSlice) MoveAndAppendTo(dest SpanLinkSlice) { + if *dest.orig == nil { + // We can simply move the entire vector and avoid any allocations. + *dest.orig = *es.orig + } else { + *dest.orig = append(*dest.orig, *es.orig...) + } + *es.orig = nil +} + +// CopyTo copies all elements from the current slice to the dest. +func (es SpanLinkSlice) CopyTo(dest SpanLinkSlice) { + srcLen := es.Len() + destCap := cap(*dest.orig) + if srcLen <= destCap { + (*dest.orig) = (*dest.orig)[:srcLen:destCap] + for i := range *es.orig { + newSpanLink((*es.orig)[i]).CopyTo(newSpanLink((*dest.orig)[i])) + } + return + } + origs := make([]otlptrace.Span_Link, srcLen) + wrappers := make([]*otlptrace.Span_Link, srcLen) + for i := range *es.orig { + wrappers[i] = &origs[i] + newSpanLink((*es.orig)[i]).CopyTo(newSpanLink(wrappers[i])) + } + *dest.orig = wrappers +} + +// Resize is an operation that resizes the slice: +// 1. If the newLen <= len then equivalent with slice[0:newLen:cap]. +// 2. If the newLen > len then (newLen - cap) empty elements will be appended to the slice. +// +// Here is how a new SpanLinkSlice can be initialized: +// es := NewSpanLinkSlice() +// es.Resize(4) +// for i := 0; i < es.Len(); i++ { +// e := es.At(i) +// // Here should set all the values for e. +// } +func (es SpanLinkSlice) Resize(newLen int) { + oldLen := len(*es.orig) + oldCap := cap(*es.orig) + if newLen <= oldLen { + *es.orig = (*es.orig)[:newLen:oldCap] + return + } + + if newLen > oldCap { + newOrig := make([]*otlptrace.Span_Link, oldLen, newLen) + copy(newOrig, *es.orig) + *es.orig = newOrig + } + + // Add extra empty elements to the array. + extraOrigs := make([]otlptrace.Span_Link, newLen-oldLen) + for i := range extraOrigs { + *es.orig = append(*es.orig, &extraOrigs[i]) + } +} + +// Append will increase the length of the SpanLinkSlice by one and set the +// given SpanLink at that new position. The original SpanLink +// could still be referenced so do not reuse it after passing it to this +// method. +func (es SpanLinkSlice) Append(e SpanLink) { + *es.orig = append(*es.orig, e.orig) +} + +// SpanLink is a pointer from the current span to another span in the same trace or in a +// different trace. See OTLP for link definition. +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpanLink function to create new instances. +// Important: zero-initialized instance is not valid for use. +type SpanLink struct { + orig *otlptrace.Span_Link +} + +func newSpanLink(orig *otlptrace.Span_Link) SpanLink { + return SpanLink{orig: orig} +} + +// NewSpanLink creates a new empty SpanLink. +// +// This must be used only in testing code since no "Set" method available. +func NewSpanLink() SpanLink { + return newSpanLink(&otlptrace.Span_Link{}) +} + +// TraceID returns the traceid associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) TraceID() TraceID { + return TraceID((*ms.orig).TraceId) +} + +// SetTraceID replaces the traceid associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) SetTraceID(v TraceID) { + (*ms.orig).TraceId = data.TraceID(v) +} + +// SpanID returns the spanid associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) SpanID() SpanID { + return SpanID((*ms.orig).SpanId) +} + +// SetSpanID replaces the spanid associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) SetSpanID(v SpanID) { + (*ms.orig).SpanId = data.SpanID(v) +} + +// TraceState returns the tracestate associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) TraceState() TraceState { + return TraceState((*ms.orig).TraceState) +} + +// SetTraceState replaces the tracestate associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) SetTraceState(v TraceState) { + (*ms.orig).TraceState = string(v) +} + +// Attributes returns the Attributes associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) Attributes() AttributeMap { + return newAttributeMap(&(*ms.orig).Attributes) +} + +// DroppedAttributesCount returns the droppedattributescount associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) DroppedAttributesCount() uint32 { + return (*ms.orig).DroppedAttributesCount +} + +// SetDroppedAttributesCount replaces the droppedattributescount associated with this SpanLink. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanLink) SetDroppedAttributesCount(v uint32) { + (*ms.orig).DroppedAttributesCount = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms SpanLink) CopyTo(dest SpanLink) { + dest.SetTraceID(ms.TraceID()) + dest.SetSpanID(ms.SpanID()) + dest.SetTraceState(ms.TraceState()) + ms.Attributes().CopyTo(dest.Attributes()) + dest.SetDroppedAttributesCount(ms.DroppedAttributesCount()) +} + +// SpanStatus is an optional final status for this span. Semantically when Status wasn't set +// it is means span ended without errors and assume Status.Ok (code = 0). +// +// This is a reference type, if passed by value and callee modifies it the +// caller will see the modification. +// +// Must use NewSpanStatus function to create new instances. +// Important: zero-initialized instance is not valid for use. +type SpanStatus struct { + orig *otlptrace.Status +} + +func newSpanStatus(orig *otlptrace.Status) SpanStatus { + return SpanStatus{orig: orig} +} + +// NewSpanStatus creates a new empty SpanStatus. +// +// This must be used only in testing code since no "Set" method available. +func NewSpanStatus() SpanStatus { + return newSpanStatus(&otlptrace.Status{}) +} + +// Code returns the code associated with this SpanStatus. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanStatus) Code() StatusCode { + return StatusCode((*ms.orig).Code) +} + +// DeprecatedCode returns the deprecatedcode associated with this SpanStatus. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanStatus) DeprecatedCode() DeprecatedStatusCode { + return DeprecatedStatusCode((*ms.orig).DeprecatedCode) +} + +// SetDeprecatedCode replaces the deprecatedcode associated with this SpanStatus. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanStatus) SetDeprecatedCode(v DeprecatedStatusCode) { + (*ms.orig).DeprecatedCode = otlptrace.Status_DeprecatedStatusCode(v) +} + +// Message returns the message associated with this SpanStatus. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanStatus) Message() string { + return (*ms.orig).Message +} + +// SetMessage replaces the message associated with this SpanStatus. +// +// Important: This causes a runtime error if IsNil() returns "true". +func (ms SpanStatus) SetMessage(v string) { + (*ms.orig).Message = v +} + +// CopyTo copies all properties from the current struct to the dest. +func (ms SpanStatus) CopyTo(dest SpanStatus) { + dest.SetCode(ms.Code()) + dest.SetDeprecatedCode(ms.DeprecatedCode()) + dest.SetMessage(ms.Message()) +} diff --git a/internal/otel_collector/consumer/pdata/generated_trace_test.go b/internal/otel_collector/consumer/pdata/generated_trace_test.go new file mode 100644 index 00000000000..c3188829bdc --- /dev/null +++ b/internal/otel_collector/consumer/pdata/generated_trace_test.go @@ -0,0 +1,1019 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "cmd/pdatagen/main.go". DO NOT EDIT. +// To regenerate this file run "go run cmd/pdatagen/main.go". + +package pdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +func TestResourceSpansSlice(t *testing.T) { + es := NewResourceSpansSlice() + assert.EqualValues(t, 0, es.Len()) + es = newResourceSpansSlice(&[]*otlptrace.ResourceSpans{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewResourceSpans() + testVal := generateTestResourceSpans() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestResourceSpans(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestResourceSpansSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestResourceSpansSlice() + dest := NewResourceSpansSlice() + src := generateTestResourceSpansSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestResourceSpansSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestResourceSpansSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestResourceSpansSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestResourceSpansSlice_CopyTo(t *testing.T) { + dest := NewResourceSpansSlice() + // Test CopyTo to empty + NewResourceSpansSlice().CopyTo(dest) + assert.EqualValues(t, NewResourceSpansSlice(), dest) + + // Test CopyTo larger slice + generateTestResourceSpansSlice().CopyTo(dest) + assert.EqualValues(t, generateTestResourceSpansSlice(), dest) + + // Test CopyTo same size slice + generateTestResourceSpansSlice().CopyTo(dest) + assert.EqualValues(t, generateTestResourceSpansSlice(), dest) +} + +func TestResourceSpansSlice_Resize(t *testing.T) { + es := generateTestResourceSpansSlice() + emptyVal := NewResourceSpans() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlptrace.ResourceSpans]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlptrace.ResourceSpans]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlptrace.ResourceSpans]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlptrace.ResourceSpans]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestResourceSpansSlice_Append(t *testing.T) { + es := generateTestResourceSpansSlice() + + emptyVal := NewResourceSpans() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewResourceSpans() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestResourceSpans_CopyTo(t *testing.T) { + ms := NewResourceSpans() + generateTestResourceSpans().CopyTo(ms) + assert.EqualValues(t, generateTestResourceSpans(), ms) +} + +func TestResourceSpans_Resource(t *testing.T) { + ms := NewResourceSpans() + fillTestResource(ms.Resource()) + assert.EqualValues(t, generateTestResource(), ms.Resource()) +} + +func TestResourceSpans_InstrumentationLibrarySpans(t *testing.T) { + ms := NewResourceSpans() + assert.EqualValues(t, NewInstrumentationLibrarySpansSlice(), ms.InstrumentationLibrarySpans()) + fillTestInstrumentationLibrarySpansSlice(ms.InstrumentationLibrarySpans()) + testValInstrumentationLibrarySpans := generateTestInstrumentationLibrarySpansSlice() + assert.EqualValues(t, testValInstrumentationLibrarySpans, ms.InstrumentationLibrarySpans()) +} + +func TestInstrumentationLibrarySpansSlice(t *testing.T) { + es := NewInstrumentationLibrarySpansSlice() + assert.EqualValues(t, 0, es.Len()) + es = newInstrumentationLibrarySpansSlice(&[]*otlptrace.InstrumentationLibrarySpans{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewInstrumentationLibrarySpans() + testVal := generateTestInstrumentationLibrarySpans() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestInstrumentationLibrarySpans(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestInstrumentationLibrarySpansSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestInstrumentationLibrarySpansSlice() + dest := NewInstrumentationLibrarySpansSlice() + src := generateTestInstrumentationLibrarySpansSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibrarySpansSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibrarySpansSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestInstrumentationLibrarySpansSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestInstrumentationLibrarySpansSlice_CopyTo(t *testing.T) { + dest := NewInstrumentationLibrarySpansSlice() + // Test CopyTo to empty + NewInstrumentationLibrarySpansSlice().CopyTo(dest) + assert.EqualValues(t, NewInstrumentationLibrarySpansSlice(), dest) + + // Test CopyTo larger slice + generateTestInstrumentationLibrarySpansSlice().CopyTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibrarySpansSlice(), dest) + + // Test CopyTo same size slice + generateTestInstrumentationLibrarySpansSlice().CopyTo(dest) + assert.EqualValues(t, generateTestInstrumentationLibrarySpansSlice(), dest) +} + +func TestInstrumentationLibrarySpansSlice_Resize(t *testing.T) { + es := generateTestInstrumentationLibrarySpansSlice() + emptyVal := NewInstrumentationLibrarySpans() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlptrace.InstrumentationLibrarySpans]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlptrace.InstrumentationLibrarySpans]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlptrace.InstrumentationLibrarySpans]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlptrace.InstrumentationLibrarySpans]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestInstrumentationLibrarySpansSlice_Append(t *testing.T) { + es := generateTestInstrumentationLibrarySpansSlice() + + emptyVal := NewInstrumentationLibrarySpans() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewInstrumentationLibrarySpans() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestInstrumentationLibrarySpans_CopyTo(t *testing.T) { + ms := NewInstrumentationLibrarySpans() + generateTestInstrumentationLibrarySpans().CopyTo(ms) + assert.EqualValues(t, generateTestInstrumentationLibrarySpans(), ms) +} + +func TestInstrumentationLibrarySpans_InstrumentationLibrary(t *testing.T) { + ms := NewInstrumentationLibrarySpans() + fillTestInstrumentationLibrary(ms.InstrumentationLibrary()) + assert.EqualValues(t, generateTestInstrumentationLibrary(), ms.InstrumentationLibrary()) +} + +func TestInstrumentationLibrarySpans_Spans(t *testing.T) { + ms := NewInstrumentationLibrarySpans() + assert.EqualValues(t, NewSpanSlice(), ms.Spans()) + fillTestSpanSlice(ms.Spans()) + testValSpans := generateTestSpanSlice() + assert.EqualValues(t, testValSpans, ms.Spans()) +} + +func TestSpanSlice(t *testing.T) { + es := NewSpanSlice() + assert.EqualValues(t, 0, es.Len()) + es = newSpanSlice(&[]*otlptrace.Span{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewSpan() + testVal := generateTestSpan() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestSpan(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestSpanSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestSpanSlice() + dest := NewSpanSlice() + src := generateTestSpanSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestSpanSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestSpanSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestSpanSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestSpanSlice_CopyTo(t *testing.T) { + dest := NewSpanSlice() + // Test CopyTo to empty + NewSpanSlice().CopyTo(dest) + assert.EqualValues(t, NewSpanSlice(), dest) + + // Test CopyTo larger slice + generateTestSpanSlice().CopyTo(dest) + assert.EqualValues(t, generateTestSpanSlice(), dest) + + // Test CopyTo same size slice + generateTestSpanSlice().CopyTo(dest) + assert.EqualValues(t, generateTestSpanSlice(), dest) +} + +func TestSpanSlice_Resize(t *testing.T) { + es := generateTestSpanSlice() + emptyVal := NewSpan() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlptrace.Span]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlptrace.Span]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlptrace.Span]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlptrace.Span]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestSpanSlice_Append(t *testing.T) { + es := generateTestSpanSlice() + + emptyVal := NewSpan() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewSpan() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestSpan_CopyTo(t *testing.T) { + ms := NewSpan() + generateTestSpan().CopyTo(ms) + assert.EqualValues(t, generateTestSpan(), ms) +} + +func TestSpan_TraceID(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, NewTraceID([16]byte{}), ms.TraceID()) + testValTraceID := NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}) + ms.SetTraceID(testValTraceID) + assert.EqualValues(t, testValTraceID, ms.TraceID()) +} + +func TestSpan_SpanID(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, NewSpanID([8]byte{}), ms.SpanID()) + testValSpanID := NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + ms.SetSpanID(testValSpanID) + assert.EqualValues(t, testValSpanID, ms.SpanID()) +} + +func TestSpan_TraceState(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, TraceState(""), ms.TraceState()) + testValTraceState := TraceState("congo=congos") + ms.SetTraceState(testValTraceState) + assert.EqualValues(t, testValTraceState, ms.TraceState()) +} + +func TestSpan_ParentSpanID(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, NewSpanID([8]byte{}), ms.ParentSpanID()) + testValParentSpanID := NewSpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}) + ms.SetParentSpanID(testValParentSpanID) + assert.EqualValues(t, testValParentSpanID, ms.ParentSpanID()) +} + +func TestSpan_Name(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, "", ms.Name()) + testValName := "test_name" + ms.SetName(testValName) + assert.EqualValues(t, testValName, ms.Name()) +} + +func TestSpan_Kind(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, SpanKindUNSPECIFIED, ms.Kind()) + testValKind := SpanKindSERVER + ms.SetKind(testValKind) + assert.EqualValues(t, testValKind, ms.Kind()) +} + +func TestSpan_StartTime(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, TimestampUnixNano(0), ms.StartTime()) + testValStartTime := TimestampUnixNano(1234567890) + ms.SetStartTime(testValStartTime) + assert.EqualValues(t, testValStartTime, ms.StartTime()) +} + +func TestSpan_EndTime(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, TimestampUnixNano(0), ms.EndTime()) + testValEndTime := TimestampUnixNano(1234567890) + ms.SetEndTime(testValEndTime) + assert.EqualValues(t, testValEndTime, ms.EndTime()) +} + +func TestSpan_Attributes(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, NewAttributeMap(), ms.Attributes()) + fillTestAttributeMap(ms.Attributes()) + testValAttributes := generateTestAttributeMap() + assert.EqualValues(t, testValAttributes, ms.Attributes()) +} + +func TestSpan_DroppedAttributesCount(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, uint32(0), ms.DroppedAttributesCount()) + testValDroppedAttributesCount := uint32(17) + ms.SetDroppedAttributesCount(testValDroppedAttributesCount) + assert.EqualValues(t, testValDroppedAttributesCount, ms.DroppedAttributesCount()) +} + +func TestSpan_Events(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, NewSpanEventSlice(), ms.Events()) + fillTestSpanEventSlice(ms.Events()) + testValEvents := generateTestSpanEventSlice() + assert.EqualValues(t, testValEvents, ms.Events()) +} + +func TestSpan_DroppedEventsCount(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, uint32(0), ms.DroppedEventsCount()) + testValDroppedEventsCount := uint32(17) + ms.SetDroppedEventsCount(testValDroppedEventsCount) + assert.EqualValues(t, testValDroppedEventsCount, ms.DroppedEventsCount()) +} + +func TestSpan_Links(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, NewSpanLinkSlice(), ms.Links()) + fillTestSpanLinkSlice(ms.Links()) + testValLinks := generateTestSpanLinkSlice() + assert.EqualValues(t, testValLinks, ms.Links()) +} + +func TestSpan_DroppedLinksCount(t *testing.T) { + ms := NewSpan() + assert.EqualValues(t, uint32(0), ms.DroppedLinksCount()) + testValDroppedLinksCount := uint32(17) + ms.SetDroppedLinksCount(testValDroppedLinksCount) + assert.EqualValues(t, testValDroppedLinksCount, ms.DroppedLinksCount()) +} + +func TestSpan_Status(t *testing.T) { + ms := NewSpan() + fillTestSpanStatus(ms.Status()) + assert.EqualValues(t, generateTestSpanStatus(), ms.Status()) +} + +func TestSpanEventSlice(t *testing.T) { + es := NewSpanEventSlice() + assert.EqualValues(t, 0, es.Len()) + es = newSpanEventSlice(&[]*otlptrace.Span_Event{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewSpanEvent() + testVal := generateTestSpanEvent() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestSpanEvent(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestSpanEventSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestSpanEventSlice() + dest := NewSpanEventSlice() + src := generateTestSpanEventSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestSpanEventSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestSpanEventSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestSpanEventSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestSpanEventSlice_CopyTo(t *testing.T) { + dest := NewSpanEventSlice() + // Test CopyTo to empty + NewSpanEventSlice().CopyTo(dest) + assert.EqualValues(t, NewSpanEventSlice(), dest) + + // Test CopyTo larger slice + generateTestSpanEventSlice().CopyTo(dest) + assert.EqualValues(t, generateTestSpanEventSlice(), dest) + + // Test CopyTo same size slice + generateTestSpanEventSlice().CopyTo(dest) + assert.EqualValues(t, generateTestSpanEventSlice(), dest) +} + +func TestSpanEventSlice_Resize(t *testing.T) { + es := generateTestSpanEventSlice() + emptyVal := NewSpanEvent() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlptrace.Span_Event]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlptrace.Span_Event]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlptrace.Span_Event]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlptrace.Span_Event]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestSpanEventSlice_Append(t *testing.T) { + es := generateTestSpanEventSlice() + + emptyVal := NewSpanEvent() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewSpanEvent() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestSpanEvent_CopyTo(t *testing.T) { + ms := NewSpanEvent() + generateTestSpanEvent().CopyTo(ms) + assert.EqualValues(t, generateTestSpanEvent(), ms) +} + +func TestSpanEvent_Timestamp(t *testing.T) { + ms := NewSpanEvent() + assert.EqualValues(t, TimestampUnixNano(0), ms.Timestamp()) + testValTimestamp := TimestampUnixNano(1234567890) + ms.SetTimestamp(testValTimestamp) + assert.EqualValues(t, testValTimestamp, ms.Timestamp()) +} + +func TestSpanEvent_Name(t *testing.T) { + ms := NewSpanEvent() + assert.EqualValues(t, "", ms.Name()) + testValName := "test_name" + ms.SetName(testValName) + assert.EqualValues(t, testValName, ms.Name()) +} + +func TestSpanEvent_Attributes(t *testing.T) { + ms := NewSpanEvent() + assert.EqualValues(t, NewAttributeMap(), ms.Attributes()) + fillTestAttributeMap(ms.Attributes()) + testValAttributes := generateTestAttributeMap() + assert.EqualValues(t, testValAttributes, ms.Attributes()) +} + +func TestSpanEvent_DroppedAttributesCount(t *testing.T) { + ms := NewSpanEvent() + assert.EqualValues(t, uint32(0), ms.DroppedAttributesCount()) + testValDroppedAttributesCount := uint32(17) + ms.SetDroppedAttributesCount(testValDroppedAttributesCount) + assert.EqualValues(t, testValDroppedAttributesCount, ms.DroppedAttributesCount()) +} + +func TestSpanLinkSlice(t *testing.T) { + es := NewSpanLinkSlice() + assert.EqualValues(t, 0, es.Len()) + es = newSpanLinkSlice(&[]*otlptrace.Span_Link{}) + assert.EqualValues(t, 0, es.Len()) + + es.Resize(7) + emptyVal := NewSpanLink() + testVal := generateTestSpanLink() + assert.EqualValues(t, 7, es.Len()) + for i := 0; i < es.Len(); i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + fillTestSpanLink(es.At(i)) + assert.EqualValues(t, testVal, es.At(i)) + } +} + +func TestSpanLinkSlice_MoveAndAppendTo(t *testing.T) { + // Test MoveAndAppendTo to empty + expectedSlice := generateTestSpanLinkSlice() + dest := NewSpanLinkSlice() + src := generateTestSpanLinkSlice() + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestSpanLinkSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo empty slice + src.MoveAndAppendTo(dest) + assert.EqualValues(t, generateTestSpanLinkSlice(), dest) + assert.EqualValues(t, 0, src.Len()) + assert.EqualValues(t, expectedSlice.Len(), dest.Len()) + + // Test MoveAndAppendTo not empty slice + generateTestSpanLinkSlice().MoveAndAppendTo(dest) + assert.EqualValues(t, 2*expectedSlice.Len(), dest.Len()) + for i := 0; i < expectedSlice.Len(); i++ { + assert.EqualValues(t, expectedSlice.At(i), dest.At(i)) + assert.EqualValues(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len())) + } +} + +func TestSpanLinkSlice_CopyTo(t *testing.T) { + dest := NewSpanLinkSlice() + // Test CopyTo to empty + NewSpanLinkSlice().CopyTo(dest) + assert.EqualValues(t, NewSpanLinkSlice(), dest) + + // Test CopyTo larger slice + generateTestSpanLinkSlice().CopyTo(dest) + assert.EqualValues(t, generateTestSpanLinkSlice(), dest) + + // Test CopyTo same size slice + generateTestSpanLinkSlice().CopyTo(dest) + assert.EqualValues(t, generateTestSpanLinkSlice(), dest) +} + +func TestSpanLinkSlice_Resize(t *testing.T) { + es := generateTestSpanLinkSlice() + emptyVal := NewSpanLink() + // Test Resize less elements. + const resizeSmallLen = 4 + expectedEs := make(map[*otlptrace.Span_Link]bool, resizeSmallLen) + for i := 0; i < resizeSmallLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, resizeSmallLen, len(expectedEs)) + es.Resize(resizeSmallLen) + assert.Equal(t, resizeSmallLen, es.Len()) + foundEs := make(map[*otlptrace.Span_Link]bool, resizeSmallLen) + for i := 0; i < es.Len(); i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + + // Test Resize more elements. + const resizeLargeLen = 7 + oldLen := es.Len() + expectedEs = make(map[*otlptrace.Span_Link]bool, oldLen) + for i := 0; i < oldLen; i++ { + expectedEs[es.At(i).orig] = true + } + assert.Equal(t, oldLen, len(expectedEs)) + es.Resize(resizeLargeLen) + assert.Equal(t, resizeLargeLen, es.Len()) + foundEs = make(map[*otlptrace.Span_Link]bool, oldLen) + for i := 0; i < oldLen; i++ { + foundEs[es.At(i).orig] = true + } + assert.EqualValues(t, expectedEs, foundEs) + for i := oldLen; i < resizeLargeLen; i++ { + assert.EqualValues(t, emptyVal, es.At(i)) + } + + // Test Resize 0 elements. + es.Resize(0) + assert.Equal(t, 0, es.Len()) +} + +func TestSpanLinkSlice_Append(t *testing.T) { + es := generateTestSpanLinkSlice() + + emptyVal := NewSpanLink() + es.Append(emptyVal) + assert.EqualValues(t, es.At(7).orig, emptyVal.orig) + + emptyVal2 := NewSpanLink() + es.Append(emptyVal2) + assert.EqualValues(t, es.At(8).orig, emptyVal2.orig) + + assert.Equal(t, 9, es.Len()) +} + +func TestSpanLink_CopyTo(t *testing.T) { + ms := NewSpanLink() + generateTestSpanLink().CopyTo(ms) + assert.EqualValues(t, generateTestSpanLink(), ms) +} + +func TestSpanLink_TraceID(t *testing.T) { + ms := NewSpanLink() + assert.EqualValues(t, NewTraceID([16]byte{}), ms.TraceID()) + testValTraceID := NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}) + ms.SetTraceID(testValTraceID) + assert.EqualValues(t, testValTraceID, ms.TraceID()) +} + +func TestSpanLink_SpanID(t *testing.T) { + ms := NewSpanLink() + assert.EqualValues(t, NewSpanID([8]byte{}), ms.SpanID()) + testValSpanID := NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + ms.SetSpanID(testValSpanID) + assert.EqualValues(t, testValSpanID, ms.SpanID()) +} + +func TestSpanLink_TraceState(t *testing.T) { + ms := NewSpanLink() + assert.EqualValues(t, TraceState(""), ms.TraceState()) + testValTraceState := TraceState("congo=congos") + ms.SetTraceState(testValTraceState) + assert.EqualValues(t, testValTraceState, ms.TraceState()) +} + +func TestSpanLink_Attributes(t *testing.T) { + ms := NewSpanLink() + assert.EqualValues(t, NewAttributeMap(), ms.Attributes()) + fillTestAttributeMap(ms.Attributes()) + testValAttributes := generateTestAttributeMap() + assert.EqualValues(t, testValAttributes, ms.Attributes()) +} + +func TestSpanLink_DroppedAttributesCount(t *testing.T) { + ms := NewSpanLink() + assert.EqualValues(t, uint32(0), ms.DroppedAttributesCount()) + testValDroppedAttributesCount := uint32(17) + ms.SetDroppedAttributesCount(testValDroppedAttributesCount) + assert.EqualValues(t, testValDroppedAttributesCount, ms.DroppedAttributesCount()) +} + +func TestSpanStatus_CopyTo(t *testing.T) { + ms := NewSpanStatus() + generateTestSpanStatus().CopyTo(ms) + assert.EqualValues(t, generateTestSpanStatus(), ms) +} + +func TestSpanStatus_Code(t *testing.T) { + ms := NewSpanStatus() + assert.EqualValues(t, StatusCode(0), ms.Code()) + testValCode := StatusCode(1) + ms.SetCode(testValCode) + assert.EqualValues(t, testValCode, ms.Code()) +} + +func TestSpanStatus_DeprecatedCode(t *testing.T) { + ms := NewSpanStatus() + assert.EqualValues(t, DeprecatedStatusCode(0), ms.DeprecatedCode()) + testValDeprecatedCode := DeprecatedStatusCode(1) + ms.SetDeprecatedCode(testValDeprecatedCode) + assert.EqualValues(t, testValDeprecatedCode, ms.DeprecatedCode()) +} + +func TestSpanStatus_Message(t *testing.T) { + ms := NewSpanStatus() + assert.EqualValues(t, "", ms.Message()) + testValMessage := "cancelled" + ms.SetMessage(testValMessage) + assert.EqualValues(t, testValMessage, ms.Message()) +} + +func generateTestResourceSpansSlice() ResourceSpansSlice { + tv := NewResourceSpansSlice() + fillTestResourceSpansSlice(tv) + return tv +} + +func fillTestResourceSpansSlice(tv ResourceSpansSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestResourceSpans(tv.At(i)) + } +} + +func generateTestResourceSpans() ResourceSpans { + tv := NewResourceSpans() + fillTestResourceSpans(tv) + return tv +} + +func fillTestResourceSpans(tv ResourceSpans) { + fillTestResource(tv.Resource()) + fillTestInstrumentationLibrarySpansSlice(tv.InstrumentationLibrarySpans()) +} + +func generateTestInstrumentationLibrarySpansSlice() InstrumentationLibrarySpansSlice { + tv := NewInstrumentationLibrarySpansSlice() + fillTestInstrumentationLibrarySpansSlice(tv) + return tv +} + +func fillTestInstrumentationLibrarySpansSlice(tv InstrumentationLibrarySpansSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestInstrumentationLibrarySpans(tv.At(i)) + } +} + +func generateTestInstrumentationLibrarySpans() InstrumentationLibrarySpans { + tv := NewInstrumentationLibrarySpans() + fillTestInstrumentationLibrarySpans(tv) + return tv +} + +func fillTestInstrumentationLibrarySpans(tv InstrumentationLibrarySpans) { + fillTestInstrumentationLibrary(tv.InstrumentationLibrary()) + fillTestSpanSlice(tv.Spans()) +} + +func generateTestSpanSlice() SpanSlice { + tv := NewSpanSlice() + fillTestSpanSlice(tv) + return tv +} + +func fillTestSpanSlice(tv SpanSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestSpan(tv.At(i)) + } +} + +func generateTestSpan() Span { + tv := NewSpan() + fillTestSpan(tv) + return tv +} + +func fillTestSpan(tv Span) { + tv.SetTraceID(NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})) + tv.SetSpanID(NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + tv.SetTraceState(TraceState("congo=congos")) + tv.SetParentSpanID(NewSpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1})) + tv.SetName("test_name") + tv.SetKind(SpanKindSERVER) + tv.SetStartTime(TimestampUnixNano(1234567890)) + tv.SetEndTime(TimestampUnixNano(1234567890)) + fillTestAttributeMap(tv.Attributes()) + tv.SetDroppedAttributesCount(uint32(17)) + fillTestSpanEventSlice(tv.Events()) + tv.SetDroppedEventsCount(uint32(17)) + fillTestSpanLinkSlice(tv.Links()) + tv.SetDroppedLinksCount(uint32(17)) + fillTestSpanStatus(tv.Status()) +} + +func generateTestSpanEventSlice() SpanEventSlice { + tv := NewSpanEventSlice() + fillTestSpanEventSlice(tv) + return tv +} + +func fillTestSpanEventSlice(tv SpanEventSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestSpanEvent(tv.At(i)) + } +} + +func generateTestSpanEvent() SpanEvent { + tv := NewSpanEvent() + fillTestSpanEvent(tv) + return tv +} + +func fillTestSpanEvent(tv SpanEvent) { + tv.SetTimestamp(TimestampUnixNano(1234567890)) + tv.SetName("test_name") + fillTestAttributeMap(tv.Attributes()) + tv.SetDroppedAttributesCount(uint32(17)) +} + +func generateTestSpanLinkSlice() SpanLinkSlice { + tv := NewSpanLinkSlice() + fillTestSpanLinkSlice(tv) + return tv +} + +func fillTestSpanLinkSlice(tv SpanLinkSlice) { + tv.Resize(7) + for i := 0; i < tv.Len(); i++ { + fillTestSpanLink(tv.At(i)) + } +} + +func generateTestSpanLink() SpanLink { + tv := NewSpanLink() + fillTestSpanLink(tv) + return tv +} + +func fillTestSpanLink(tv SpanLink) { + tv.SetTraceID(NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})) + tv.SetSpanID(NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + tv.SetTraceState(TraceState("congo=congos")) + fillTestAttributeMap(tv.Attributes()) + tv.SetDroppedAttributesCount(uint32(17)) +} + +func generateTestSpanStatus() SpanStatus { + tv := NewSpanStatus() + fillTestSpanStatus(tv) + return tv +} + +func fillTestSpanStatus(tv SpanStatus) { + tv.SetCode(StatusCode(1)) + tv.SetDeprecatedCode(DeprecatedStatusCode(1)) + tv.SetMessage("cancelled") +} diff --git a/internal/otel_collector/consumer/pdata/log.go b/internal/otel_collector/consumer/pdata/log.go new file mode 100644 index 00000000000..fef6a4f2aa0 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/log.go @@ -0,0 +1,142 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "go.opentelemetry.io/collector/internal" + otlpcollectorlog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +// This file defines in-memory data structures to represent logs. + +// Logs is the top-level struct that is propagated through the logs pipeline. +// +// This is a reference type (like builtin map). +// +// Must use NewLogs functions to create new instances. +// Important: zero-initialized instance is not valid for use. +type Logs struct { + orig *[]*otlplogs.ResourceLogs +} + +// NewLogs creates a new Logs. +func NewLogs() Logs { + orig := []*otlplogs.ResourceLogs(nil) + return Logs{&orig} +} + +// LogsFromInternalRep creates the internal Logs representation from the ProtoBuf. Should +// not be used outside this module. This is intended to be used only by OTLP exporter and +// File exporter, which legitimately need to work with OTLP Protobuf structs. +func LogsFromInternalRep(logs internal.OtlpLogsWrapper) Logs { + return Logs{logs.Orig} +} + +// InternalRep returns internal representation of the logs. Should not be used outside +// this module. This is intended to be used only by OTLP exporter and File exporter, +// which legitimately need to work with OTLP Protobuf structs. +func (ld Logs) InternalRep() internal.OtlpLogsWrapper { + return internal.OtlpLogsWrapper{Orig: ld.orig} +} + +// ToOtlpProtoBytes returns the internal Logs to OTLP Collector ExportTraceServiceRequest +// ProtoBuf bytes. This is intended to export OTLP Protobuf bytes for OTLP/HTTP transports. +func (ld Logs) ToOtlpProtoBytes() ([]byte, error) { + logs := otlpcollectorlog.ExportLogsServiceRequest{ + ResourceLogs: *ld.orig, + } + return logs.Marshal() +} + +// FromOtlpProtoBytes converts OTLP Collector ExportLogsServiceRequest +// ProtoBuf bytes to the internal Logs. Overrides current data. +// Calling this function on zero-initialized structure causes panic. +// Use it with NewLogs or on existing initialized Logs. +func (ld Logs) FromOtlpProtoBytes(data []byte) error { + logs := otlpcollectorlog.ExportLogsServiceRequest{} + if err := logs.Unmarshal(data); err != nil { + return err + } + *ld.orig = logs.ResourceLogs + return nil +} + +// Clone returns a copy of Logs. +func (ld Logs) Clone() Logs { + rls := NewResourceLogsSlice() + ld.ResourceLogs().CopyTo(rls) + return Logs(rls) +} + +// LogRecordCount calculates the total number of log records. +func (ld Logs) LogRecordCount() int { + logCount := 0 + rss := ld.ResourceLogs() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + ill := rs.InstrumentationLibraryLogs() + for i := 0; i < ill.Len(); i++ { + logs := ill.At(i) + logCount += logs.Logs().Len() + } + } + return logCount +} + +// SizeBytes returns the number of bytes in the internal representation of the +// logs. +func (ld Logs) SizeBytes() int { + size := 0 + for i := range *ld.orig { + size += (*ld.orig)[i].Size() + } + return size +} + +func (ld Logs) ResourceLogs() ResourceLogsSlice { + return ResourceLogsSlice(ld) +} + +// SeverityNumber is the public alias of otlplogs.SeverityNumber from internal package. +type SeverityNumber otlplogs.SeverityNumber + +const ( + SeverityNumberUNDEFINED = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED) + SeverityNumberTRACE = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_TRACE) + SeverityNumberTRACE2 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_TRACE2) + SeverityNumberTRACE3 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_TRACE3) + SeverityNumberTRACE4 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_TRACE4) + SeverityNumberDEBUG = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_DEBUG) + SeverityNumberDEBUG2 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_DEBUG2) + SeverityNumberDEBUG3 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_DEBUG3) + SeverityNumberDEBUG4 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_DEBUG4) + SeverityNumberINFO = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_INFO) + SeverityNumberINFO2 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_INFO2) + SeverityNumberINFO3 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_INFO3) + SeverityNumberINFO4 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_INFO4) + SeverityNumberWARN = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_WARN) + SeverityNumberWARN2 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_WARN2) + SeverityNumberWARN3 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_WARN3) + SeverityNumberWARN4 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_WARN4) + SeverityNumberERROR = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_ERROR) + SeverityNumberERROR2 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_ERROR2) + SeverityNumberERROR3 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_ERROR3) + SeverityNumberERROR4 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_ERROR4) + SeverityNumberFATAL = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_FATAL) + SeverityNumberFATAL2 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_FATAL2) + SeverityNumberFATAL3 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_FATAL3) + SeverityNumberFATAL4 = SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_FATAL4) +) diff --git a/internal/otel_collector/consumer/pdata/log_test.go b/internal/otel_collector/consumer/pdata/log_test.go new file mode 100644 index 00000000000..c0f35c34ab4 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/log_test.go @@ -0,0 +1,134 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/internal" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +func TestLogRecordCount(t *testing.T) { + md := NewLogs() + assert.EqualValues(t, 0, md.LogRecordCount()) + + md.ResourceLogs().Resize(1) + assert.EqualValues(t, 0, md.LogRecordCount()) + + md.ResourceLogs().At(0).InstrumentationLibraryLogs().Resize(1) + assert.EqualValues(t, 0, md.LogRecordCount()) + + md.ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().Resize(1) + assert.EqualValues(t, 1, md.LogRecordCount()) + + rms := md.ResourceLogs() + rms.Resize(3) + rms.At(0).InstrumentationLibraryLogs().Resize(1) + rms.At(0).InstrumentationLibraryLogs().At(0).Logs().Resize(1) + rms.At(1).InstrumentationLibraryLogs().Resize(1) + rms.At(2).InstrumentationLibraryLogs().Resize(1) + rms.At(2).InstrumentationLibraryLogs().At(0).Logs().Resize(5) + assert.EqualValues(t, 6, md.LogRecordCount()) +} + +func TestLogRecordCountWithEmpty(t *testing.T) { + assert.EqualValues(t, 0, LogsFromInternalRep(internal.LogsFromOtlp([]*otlplogs.ResourceLogs{{}})).LogRecordCount()) + assert.EqualValues(t, 0, LogsFromInternalRep(internal.LogsFromOtlp([]*otlplogs.ResourceLogs{ + { + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{{}}, + }, + })).LogRecordCount()) + assert.EqualValues(t, 1, LogsFromInternalRep(internal.LogsFromOtlp([]*otlplogs.ResourceLogs{ + { + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{{}}, + }, + }, + }, + })).LogRecordCount()) +} + +func TestToFromLogProto(t *testing.T) { + otlp := []*otlplogs.ResourceLogs(nil) + td := LogsFromInternalRep(internal.LogsFromOtlp(otlp)) + assert.EqualValues(t, NewLogs(), td) + assert.EqualValues(t, otlp, *td.orig) +} + +func TestLogsToFromOtlpProtoBytes(t *testing.T) { + send := NewLogs() + fillTestResourceLogsSlice(send.ResourceLogs()) + bytes, err := send.ToOtlpProtoBytes() + assert.NoError(t, err) + + recv := NewLogs() + err = recv.FromOtlpProtoBytes(bytes) + assert.NoError(t, err) + assert.EqualValues(t, send, recv) +} + +func TestLogsFromInvalidOtlpProtoBytes(t *testing.T) { + err := NewLogs().FromOtlpProtoBytes([]byte{0xFF}) + assert.EqualError(t, err, "unexpected EOF") +} + +func TestLogsClone(t *testing.T) { + logs := NewLogs() + fillTestResourceLogsSlice(logs.ResourceLogs()) + assert.EqualValues(t, logs, logs.Clone()) +} + +func BenchmarkLogsClone(b *testing.B) { + logs := NewLogs() + fillTestResourceLogsSlice(logs.ResourceLogs()) + b.ResetTimer() + for n := 0; n < b.N; n++ { + clone := logs.Clone() + if clone.ResourceLogs().Len() != logs.ResourceLogs().Len() { + b.Fail() + } + } +} + +func BenchmarkLogsToOtlp(b *testing.B) { + traces := NewLogs() + fillTestResourceLogsSlice(traces.ResourceLogs()) + b.ResetTimer() + for n := 0; n < b.N; n++ { + buf, err := traces.ToOtlpProtoBytes() + require.NoError(b, err) + assert.NotEqual(b, 0, len(buf)) + } +} + +func BenchmarkLogsFromOtlp(b *testing.B) { + baseLogs := NewLogs() + fillTestResourceLogsSlice(baseLogs.ResourceLogs()) + buf, err := baseLogs.ToOtlpProtoBytes() + require.NoError(b, err) + assert.NotEqual(b, 0, len(buf)) + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + traces := NewLogs() + require.NoError(b, traces.FromOtlpProtoBytes(buf)) + assert.Equal(b, baseLogs.ResourceLogs().Len(), traces.ResourceLogs().Len()) + } +} diff --git a/internal/otel_collector/consumer/pdata/metric.go b/internal/otel_collector/consumer/pdata/metric.go new file mode 100644 index 00000000000..d415c9f2001 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/metric.go @@ -0,0 +1,314 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + otlpcollectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +type AggregationTemporality otlpmetrics.AggregationTemporality + +const ( + AggregationTemporalityUnspecified = AggregationTemporality(otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED) + AggregationTemporalityDelta = AggregationTemporality(otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA) + AggregationTemporalityCumulative = AggregationTemporality(otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE) +) + +func (at AggregationTemporality) String() string { + return otlpmetrics.AggregationTemporality(at).String() +} + +// Metrics is an opaque interface that allows transition to the new internal Metrics data, but also facilitate the +// transition to the new components especially for traces. +// +// Outside of the core repository the metrics pipeline cannot be converted to the new model since data.MetricData is +// part of the internal package. +type Metrics struct { + orig *[]*otlpmetrics.ResourceMetrics +} + +// NewMetricData creates a new MetricData. +func NewMetrics() Metrics { + orig := []*otlpmetrics.ResourceMetrics(nil) + return Metrics{&orig} +} + +// MetricDataFromOtlp creates the internal MetricData representation from the OTLP. +func MetricsFromOtlp(orig []*otlpmetrics.ResourceMetrics) Metrics { + return Metrics{&orig} +} + +// MetricDataToOtlp converts the internal MetricData to the OTLP. +func MetricsToOtlp(md Metrics) []*otlpmetrics.ResourceMetrics { + return *md.orig +} + +// ToOtlpProtoBytes returns the internal MetricData to the OTLP Collector +// ExportMetricsServiceRequest ProtoBuf bytes. This is intended to export +// OTLP Protobuf bytes for OTLP/HTTP transports. +func (md Metrics) ToOtlpProtoBytes() ([]byte, error) { + metrics := otlpcollectormetrics.ExportMetricsServiceRequest{ + ResourceMetrics: *md.orig, + } + return metrics.Marshal() +} + +// FromOtlpProtoBytes converts OTLP Collector ExportMetricsServiceRequest +// ProtoBuf bytes to the internal Metrics. Overrides current data. +// Calling this function on zero-initialized structure causes panic. +// Use it with NewMetrics or on existing initialized Metrics. +func (md Metrics) FromOtlpProtoBytes(data []byte) error { + metrics := otlpcollectormetrics.ExportMetricsServiceRequest{} + if err := metrics.Unmarshal(data); err != nil { + return err + } + *md.orig = metrics.ResourceMetrics + return nil +} + +// Clone returns a copy of MetricData. +func (md Metrics) Clone() Metrics { + rms := NewResourceMetricsSlice() + md.ResourceMetrics().CopyTo(rms) + return Metrics(rms) +} + +func (md Metrics) ResourceMetrics() ResourceMetricsSlice { + return newResourceMetricsSlice(md.orig) +} + +// MetricCount calculates the total number of metrics. +func (md Metrics) MetricCount() int { + metricCount := 0 + rms := md.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + rm := rms.At(i) + ilms := rm.InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + ilm := ilms.At(j) + metricCount += ilm.Metrics().Len() + } + } + return metricCount +} + +// Size returns size in bytes. +func (md Metrics) Size() int { + size := 0 + for i := 0; i < len(*md.orig); i++ { + if (*md.orig)[i] == nil { + continue + } + size += (*(*md.orig)[i]).Size() + } + return size +} + +// MetricAndDataPointCount calculates the total number of metrics and data points. +func (md Metrics) MetricAndDataPointCount() (metricCount int, dataPointCount int) { + rms := md.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + rm := rms.At(i) + ilms := rm.InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + ilm := ilms.At(j) + metrics := ilm.Metrics() + metricCount += metrics.Len() + ms := ilm.Metrics() + for k := 0; k < ms.Len(); k++ { + m := ms.At(k) + switch m.DataType() { + case MetricDataTypeIntGauge: + dataPointCount += m.IntGauge().DataPoints().Len() + case MetricDataTypeDoubleGauge: + dataPointCount += m.DoubleGauge().DataPoints().Len() + case MetricDataTypeIntSum: + dataPointCount += m.IntSum().DataPoints().Len() + case MetricDataTypeDoubleSum: + dataPointCount += m.DoubleSum().DataPoints().Len() + case MetricDataTypeIntHistogram: + dataPointCount += m.IntHistogram().DataPoints().Len() + case MetricDataTypeDoubleHistogram: + dataPointCount += m.DoubleHistogram().DataPoints().Len() + case MetricDataTypeDoubleSummary: + dataPointCount += m.DoubleSummary().DataPoints().Len() + } + } + } + } + return +} + +// MetricDataType specifies the type of data in a Metric. +type MetricDataType int + +const ( + MetricDataTypeNone MetricDataType = iota + MetricDataTypeIntGauge + MetricDataTypeDoubleGauge + MetricDataTypeIntSum + MetricDataTypeDoubleSum + MetricDataTypeIntHistogram + MetricDataTypeDoubleHistogram + MetricDataTypeDoubleSummary +) + +func (mdt MetricDataType) String() string { + switch mdt { + case MetricDataTypeNone: + return "None" + case MetricDataTypeIntGauge: + return "IntGauge" + case MetricDataTypeDoubleGauge: + return "DoubleGauge" + case MetricDataTypeIntSum: + return "IntSum" + case MetricDataTypeDoubleSum: + return "DoubleSum" + case MetricDataTypeIntHistogram: + return "IntHistogram" + case MetricDataTypeDoubleHistogram: + return "DoubleHistogram" + case MetricDataTypeDoubleSummary: + return "DoubleSummary" + } + return "" +} + +// DataType returns the type of the data for this Metric. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) DataType() MetricDataType { + switch ms.orig.Data.(type) { + case *otlpmetrics.Metric_IntGauge: + return MetricDataTypeIntGauge + case *otlpmetrics.Metric_DoubleGauge: + return MetricDataTypeDoubleGauge + case *otlpmetrics.Metric_IntSum: + return MetricDataTypeIntSum + case *otlpmetrics.Metric_DoubleSum: + return MetricDataTypeDoubleSum + case *otlpmetrics.Metric_IntHistogram: + return MetricDataTypeIntHistogram + case *otlpmetrics.Metric_DoubleHistogram: + return MetricDataTypeDoubleHistogram + case *otlpmetrics.Metric_DoubleSummary: + return MetricDataTypeDoubleSummary + } + return MetricDataTypeNone +} + +// SetDataType clears any existing data and initialize it with an empty data of the given type. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) SetDataType(ty MetricDataType) { + switch ty { + case MetricDataTypeIntGauge: + ms.orig.Data = &otlpmetrics.Metric_IntGauge{IntGauge: &otlpmetrics.IntGauge{}} + case MetricDataTypeDoubleGauge: + ms.orig.Data = &otlpmetrics.Metric_DoubleGauge{DoubleGauge: &otlpmetrics.DoubleGauge{}} + case MetricDataTypeIntSum: + ms.orig.Data = &otlpmetrics.Metric_IntSum{IntSum: &otlpmetrics.IntSum{}} + case MetricDataTypeDoubleSum: + ms.orig.Data = &otlpmetrics.Metric_DoubleSum{DoubleSum: &otlpmetrics.DoubleSum{}} + case MetricDataTypeIntHistogram: + ms.orig.Data = &otlpmetrics.Metric_IntHistogram{IntHistogram: &otlpmetrics.IntHistogram{}} + case MetricDataTypeDoubleHistogram: + ms.orig.Data = &otlpmetrics.Metric_DoubleHistogram{DoubleHistogram: &otlpmetrics.DoubleHistogram{}} + case MetricDataTypeDoubleSummary: + ms.orig.Data = &otlpmetrics.Metric_DoubleSummary{DoubleSummary: &otlpmetrics.DoubleSummary{}} + } +} + +// IntGauge returns the data as IntGauge. +// Calling this function when DataType() != MetricDataTypeIntGauge will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) IntGauge() IntGauge { + return newIntGauge(ms.orig.Data.(*otlpmetrics.Metric_IntGauge).IntGauge) +} + +// DoubleGauge returns the data as DoubleGauge. +// Calling this function when DataType() != MetricDataTypeDoubleGauge will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) DoubleGauge() DoubleGauge { + return newDoubleGauge(ms.orig.Data.(*otlpmetrics.Metric_DoubleGauge).DoubleGauge) +} + +// IntSum returns the data as IntSum. +// Calling this function when DataType() != MetricDataTypeIntSum will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) IntSum() IntSum { + return newIntSum(ms.orig.Data.(*otlpmetrics.Metric_IntSum).IntSum) +} + +// DoubleSum returns the data as DoubleSum. +// Calling this function when DataType() != MetricDataTypeDoubleSum will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) DoubleSum() DoubleSum { + return newDoubleSum(ms.orig.Data.(*otlpmetrics.Metric_DoubleSum).DoubleSum) +} + +// IntHistogram returns the data as IntHistogram. +// Calling this function when DataType() != MetricDataTypeIntHistogram will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) IntHistogram() IntHistogram { + return newIntHistogram(ms.orig.Data.(*otlpmetrics.Metric_IntHistogram).IntHistogram) +} + +// DoubleHistogram returns the data as DoubleHistogram. +// Calling this function when DataType() != MetricDataTypeDoubleHistogram will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) DoubleHistogram() DoubleHistogram { + return newDoubleHistogram(ms.orig.Data.(*otlpmetrics.Metric_DoubleHistogram).DoubleHistogram) +} + +// DoubleSummary returns the data as DoubleSummary. +// Calling this function when DataType() != MetricDataTypeDoubleSummary will cause a panic. +// Calling this function on zero-initialized Metric will cause a panic. +func (ms Metric) DoubleSummary() DoubleSummary { + return newDoubleSummary(ms.orig.Data.(*otlpmetrics.Metric_DoubleSummary).DoubleSummary) +} + +func copyData(src, dest *otlpmetrics.Metric) { + switch srcData := (src).Data.(type) { + case *otlpmetrics.Metric_IntGauge: + data := &otlpmetrics.Metric_IntGauge{IntGauge: &otlpmetrics.IntGauge{}} + newIntGauge(srcData.IntGauge).CopyTo(newIntGauge(data.IntGauge)) + dest.Data = data + case *otlpmetrics.Metric_DoubleGauge: + data := &otlpmetrics.Metric_DoubleGauge{DoubleGauge: &otlpmetrics.DoubleGauge{}} + newDoubleGauge(srcData.DoubleGauge).CopyTo(newDoubleGauge(data.DoubleGauge)) + dest.Data = data + case *otlpmetrics.Metric_IntSum: + data := &otlpmetrics.Metric_IntSum{IntSum: &otlpmetrics.IntSum{}} + newIntSum(srcData.IntSum).CopyTo(newIntSum(data.IntSum)) + dest.Data = data + case *otlpmetrics.Metric_DoubleSum: + data := &otlpmetrics.Metric_DoubleSum{DoubleSum: &otlpmetrics.DoubleSum{}} + newDoubleSum(srcData.DoubleSum).CopyTo(newDoubleSum(data.DoubleSum)) + dest.Data = data + case *otlpmetrics.Metric_IntHistogram: + data := &otlpmetrics.Metric_IntHistogram{IntHistogram: &otlpmetrics.IntHistogram{}} + newIntHistogram(srcData.IntHistogram).CopyTo(newIntHistogram(data.IntHistogram)) + dest.Data = data + case *otlpmetrics.Metric_DoubleHistogram: + data := &otlpmetrics.Metric_DoubleHistogram{DoubleHistogram: &otlpmetrics.DoubleHistogram{}} + newDoubleHistogram(srcData.DoubleHistogram).CopyTo(newDoubleHistogram(data.DoubleHistogram)) + dest.Data = data + case *otlpmetrics.Metric_DoubleSummary: + data := &otlpmetrics.Metric_DoubleSummary{DoubleSummary: &otlpmetrics.DoubleSummary{}} + newDoubleSummary(srcData.DoubleSummary).CopyTo(newDoubleSummary(data.DoubleSummary)) + dest.Data = data + } +} diff --git a/internal/otel_collector/consumer/pdata/metric_test.go b/internal/otel_collector/consumer/pdata/metric_test.go new file mode 100644 index 00000000000..70477a88558 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/metric_test.go @@ -0,0 +1,988 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "testing" + + gogoproto "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + goproto "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/emptypb" + + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" +) + +const ( + startTime = uint64(12578940000000012345) + endTime = uint64(12578940000000054321) +) + +func TestCopyData(t *testing.T) { + tests := []struct { + name string + src *otlpmetrics.Metric + }{ + { + name: "IntGauge", + src: &otlpmetrics.Metric{ + Data: &otlpmetrics.Metric_IntGauge{ + IntGauge: &otlpmetrics.IntGauge{}, + }, + }, + }, + { + name: "DoubleGauge", + src: &otlpmetrics.Metric{ + Data: &otlpmetrics.Metric_DoubleGauge{ + DoubleGauge: &otlpmetrics.DoubleGauge{}, + }, + }, + }, + { + name: "IntSum", + src: &otlpmetrics.Metric{ + Data: &otlpmetrics.Metric_IntSum{ + IntSum: &otlpmetrics.IntSum{}, + }, + }, + }, + { + name: "DoubleSum", + src: &otlpmetrics.Metric{ + Data: &otlpmetrics.Metric_DoubleSum{ + DoubleSum: &otlpmetrics.DoubleSum{}, + }, + }, + }, + { + name: "IntHistogram", + src: &otlpmetrics.Metric{ + Data: &otlpmetrics.Metric_IntHistogram{ + IntHistogram: &otlpmetrics.IntHistogram{}, + }, + }, + }, + { + name: "DoubleHistogram", + src: &otlpmetrics.Metric{ + Data: &otlpmetrics.Metric_DoubleHistogram{ + DoubleHistogram: &otlpmetrics.DoubleHistogram{}, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dest := &otlpmetrics.Metric{} + assert.Nil(t, dest.Data) + assert.NotNil(t, test.src.Data) + copyData(test.src, dest) + assert.EqualValues(t, test.src, dest) + }) + } +} + +func TestDataType(t *testing.T) { + m := NewMetric() + assert.Equal(t, MetricDataTypeNone, m.DataType()) + m.SetDataType(MetricDataTypeIntGauge) + assert.Equal(t, MetricDataTypeIntGauge, m.DataType()) + m.SetDataType(MetricDataTypeDoubleGauge) + assert.Equal(t, MetricDataTypeDoubleGauge, m.DataType()) + m.SetDataType(MetricDataTypeIntSum) + assert.Equal(t, MetricDataTypeIntSum, m.DataType()) + m.SetDataType(MetricDataTypeDoubleSum) + assert.Equal(t, MetricDataTypeDoubleSum, m.DataType()) + m.SetDataType(MetricDataTypeIntHistogram) + assert.Equal(t, MetricDataTypeIntHistogram, m.DataType()) + m.SetDataType(MetricDataTypeDoubleHistogram) + assert.Equal(t, MetricDataTypeDoubleHistogram, m.DataType()) + m.SetDataType(MetricDataTypeDoubleSummary) + assert.Equal(t, MetricDataTypeDoubleSummary, m.DataType()) +} + +func TestResourceMetricsWireCompatibility(t *testing.T) { + // This test verifies that OTLP ProtoBufs generated using goproto lib in + // opentelemetry-proto repository OTLP ProtoBufs generated using gogoproto lib in + // this repository are wire compatible. + + // Generate ResourceMetrics as pdata struct. + pdataRM := generateTestResourceMetrics() + + // Marshal its underlying ProtoBuf to wire. + wire1, err := gogoproto.Marshal(pdataRM.orig) + assert.NoError(t, err) + assert.NotNil(t, wire1) + + // Unmarshal from the wire to OTLP Protobuf in goproto's representation. + var goprotoMessage emptypb.Empty + err = goproto.Unmarshal(wire1, &goprotoMessage) + assert.NoError(t, err) + + // Marshal to the wire again. + wire2, err := goproto.Marshal(&goprotoMessage) + assert.NoError(t, err) + assert.NotNil(t, wire2) + + // Unmarshal from the wire into gogoproto's representation. + var gogoprotoRM otlpmetrics.ResourceMetrics + err = gogoproto.Unmarshal(wire2, &gogoprotoRM) + assert.NoError(t, err) + + // Now compare that the original and final ProtoBuf messages are the same. + // This proves that goproto and gogoproto marshaling/unmarshaling are wire compatible. + assert.True(t, assert.EqualValues(t, pdataRM.orig, &gogoprotoRM)) +} + +func TestMetricCount(t *testing.T) { + md := NewMetrics() + assert.EqualValues(t, 0, md.MetricCount()) + + md.ResourceMetrics().Resize(1) + assert.EqualValues(t, 0, md.MetricCount()) + + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().Resize(1) + assert.EqualValues(t, 0, md.MetricCount()) + + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().Resize(1) + assert.EqualValues(t, 1, md.MetricCount()) + + rms := md.ResourceMetrics() + rms.Resize(3) + rms.At(0).InstrumentationLibraryMetrics().Resize(1) + rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics().Resize(1) + rms.At(1).InstrumentationLibraryMetrics().Resize(1) + rms.At(2).InstrumentationLibraryMetrics().Resize(1) + rms.At(2).InstrumentationLibraryMetrics().At(0).Metrics().Resize(5) + assert.EqualValues(t, 6, md.MetricCount()) +} + +func TestMetricSize(t *testing.T) { + md := NewMetrics() + assert.Equal(t, 0, md.Size()) + rms := md.ResourceMetrics() + rms.Resize(1) + rms.At(0).InstrumentationLibraryMetrics().Resize(1) + rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics().Resize(1) + metric := rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + metric.SetDataType(MetricDataTypeDoubleHistogram) + doubleHistogram := metric.DoubleHistogram() + doubleHistogram.DataPoints().Resize(1) + doubleHistogram.DataPoints().At(0).SetCount(123) + doubleHistogram.DataPoints().At(0).SetSum(123) + otlp := MetricsToOtlp(md) + size := 0 + sizeBytes := 0 + for _, rmerics := range otlp { + size += rmerics.Size() + bts, err := rmerics.Marshal() + require.NoError(t, err) + sizeBytes += len(bts) + } + assert.Equal(t, size, md.Size()) + assert.Equal(t, sizeBytes, md.Size()) +} + +func TestMetricsSizeWithNil(t *testing.T) { + assert.Equal(t, 0, MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{nil}).Size()) +} + +func TestMetricCountWithEmpty(t *testing.T) { + assert.EqualValues(t, 0, MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{{}}).MetricCount()) + assert.EqualValues(t, 0, MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{{}}, + }, + }).MetricCount()) + assert.EqualValues(t, 1, MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{{}}, + }, + }, + }, + }).MetricCount()) +} + +func TestMetricAndDataPointCount(t *testing.T) { + md := NewMetrics() + ms, dps := md.MetricAndDataPointCount() + assert.EqualValues(t, 0, ms) + assert.EqualValues(t, 0, dps) + + rms := md.ResourceMetrics() + rms.Resize(1) + ms, dps = md.MetricAndDataPointCount() + assert.EqualValues(t, 0, ms) + assert.EqualValues(t, 0, dps) + + ilms := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics() + ilms.Resize(1) + ms, dps = md.MetricAndDataPointCount() + assert.EqualValues(t, 0, ms) + assert.EqualValues(t, 0, dps) + + ilms.At(0).Metrics().Resize(1) + ms, dps = md.MetricAndDataPointCount() + assert.EqualValues(t, 1, ms) + assert.EqualValues(t, 0, dps) + ilms.At(0).Metrics().At(0).SetDataType(MetricDataTypeIntSum) + intSum := ilms.At(0).Metrics().At(0).IntSum() + intSum.DataPoints().Resize(3) + _, dps = md.MetricAndDataPointCount() + assert.EqualValues(t, 3, dps) + + md = NewMetrics() + rms = md.ResourceMetrics() + rms.Resize(3) + rms.At(0).InstrumentationLibraryMetrics().Resize(1) + rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics().Resize(1) + rms.At(1).InstrumentationLibraryMetrics().Resize(1) + rms.At(2).InstrumentationLibraryMetrics().Resize(1) + ilms = rms.At(2).InstrumentationLibraryMetrics() + ilms.Resize(1) + ilms.At(0).Metrics().Resize(5) + ms, dps = md.MetricAndDataPointCount() + assert.EqualValues(t, 6, ms) + assert.EqualValues(t, 0, dps) + ilms.At(0).Metrics().At(1).SetDataType(MetricDataTypeDoubleGauge) + doubleGauge := ilms.At(0).Metrics().At(1).DoubleGauge() + doubleGauge.DataPoints().Resize(1) + ilms.At(0).Metrics().At(3).SetDataType(MetricDataTypeIntHistogram) + intHistogram := ilms.At(0).Metrics().At(3).IntHistogram() + intHistogram.DataPoints().Resize(3) + ms, dps = md.MetricAndDataPointCount() + assert.EqualValues(t, 6, ms) + assert.EqualValues(t, 4, dps) +} + +func TestMetricAndDataPointCountWithEmpty(t *testing.T) { + ms, dps := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{{}}).MetricAndDataPointCount() + assert.EqualValues(t, 0, ms) + assert.EqualValues(t, 0, dps) + + ms, dps = MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{{}}, + }, + }).MetricAndDataPointCount() + assert.EqualValues(t, 0, ms) + assert.EqualValues(t, 0, dps) + + ms, dps = MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{{}}, + }, + }, + }, + }).MetricAndDataPointCount() + assert.EqualValues(t, 1, ms) + assert.EqualValues(t, 0, dps) + + ms, dps = MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{{ + Data: &otlpmetrics.Metric_DoubleGauge{ + DoubleGauge: &otlpmetrics.DoubleGauge{ + DataPoints: []*otlpmetrics.DoubleDataPoint{ + {}, + }, + }, + }, + }}, + }, + }, + }, + }).MetricAndDataPointCount() + assert.EqualValues(t, 1, ms) + assert.EqualValues(t, 1, dps) + +} + +func TestMetricAndDataPointCountWithNilDataPoints(t *testing.T) { + metrics := NewMetrics() + metrics.ResourceMetrics().Resize(1) + rm := metrics.ResourceMetrics().At(0) + rm.InstrumentationLibraryMetrics().Resize(1) + ilm := rm.InstrumentationLibraryMetrics().At(0) + intGauge := NewMetric() + ilm.Metrics().Append(intGauge) + intGauge.SetDataType(MetricDataTypeIntGauge) + doubleGauge := NewMetric() + ilm.Metrics().Append(doubleGauge) + doubleGauge.SetDataType(MetricDataTypeDoubleGauge) + intHistogram := NewMetric() + ilm.Metrics().Append(intHistogram) + intHistogram.SetDataType(MetricDataTypeIntHistogram) + doubleHistogram := NewMetric() + ilm.Metrics().Append(doubleHistogram) + doubleHistogram.SetDataType(MetricDataTypeDoubleHistogram) + intSum := NewMetric() + ilm.Metrics().Append(intSum) + intSum.SetDataType(MetricDataTypeIntSum) + doubleSum := NewMetric() + ilm.Metrics().Append(doubleSum) + doubleSum.SetDataType(MetricDataTypeDoubleSum) + + ms, dps := metrics.MetricAndDataPointCount() + + assert.EqualValues(t, 6, ms) + assert.EqualValues(t, 0, dps) +} + +func TestOtlpToInternalReadOnly(t *testing.T) { + metricData := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric(), generateTestProtoDoubleSumMetric(), generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + }) + resourceMetrics := metricData.ResourceMetrics() + assert.EqualValues(t, 1, resourceMetrics.Len()) + + resourceMetric := resourceMetrics.At(0) + assert.EqualValues(t, NewAttributeMap().InitFromMap(map[string]AttributeValue{ + "string": NewAttributeValueString("string-resource"), + }), resourceMetric.Resource().Attributes()) + metrics := resourceMetric.InstrumentationLibraryMetrics().At(0).Metrics() + assert.EqualValues(t, 3, metrics.Len()) + + // Check int64 metric + metricInt := metrics.At(0) + assert.EqualValues(t, "my_metric_int", metricInt.Name()) + assert.EqualValues(t, "My metric", metricInt.Description()) + assert.EqualValues(t, "ms", metricInt.Unit()) + assert.EqualValues(t, MetricDataTypeIntGauge, metricInt.DataType()) + int64DataPoints := metricInt.IntGauge().DataPoints() + assert.EqualValues(t, 2, int64DataPoints.Len()) + // First point + assert.EqualValues(t, startTime, int64DataPoints.At(0).StartTime()) + assert.EqualValues(t, endTime, int64DataPoints.At(0).Timestamp()) + assert.EqualValues(t, 123, int64DataPoints.At(0).Value()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"key0": "value0"}), int64DataPoints.At(0).LabelsMap()) + // Second point + assert.EqualValues(t, startTime, int64DataPoints.At(1).StartTime()) + assert.EqualValues(t, endTime, int64DataPoints.At(1).Timestamp()) + assert.EqualValues(t, 456, int64DataPoints.At(1).Value()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"key1": "value1"}), int64DataPoints.At(1).LabelsMap()) + + // Check double metric + metricDouble := metrics.At(1) + assert.EqualValues(t, "my_metric_double", metricDouble.Name()) + assert.EqualValues(t, "My metric", metricDouble.Description()) + assert.EqualValues(t, "ms", metricDouble.Unit()) + assert.EqualValues(t, MetricDataTypeDoubleSum, metricDouble.DataType()) + dsd := metricDouble.DoubleSum() + assert.EqualValues(t, AggregationTemporalityCumulative, dsd.AggregationTemporality()) + doubleDataPoints := dsd.DataPoints() + assert.EqualValues(t, 2, doubleDataPoints.Len()) + // First point + assert.EqualValues(t, startTime, doubleDataPoints.At(0).StartTime()) + assert.EqualValues(t, endTime, doubleDataPoints.At(0).Timestamp()) + assert.EqualValues(t, 123.1, doubleDataPoints.At(0).Value()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"key0": "value0"}), doubleDataPoints.At(0).LabelsMap()) + // Second point + assert.EqualValues(t, startTime, doubleDataPoints.At(1).StartTime()) + assert.EqualValues(t, endTime, doubleDataPoints.At(1).Timestamp()) + assert.EqualValues(t, 456.1, doubleDataPoints.At(1).Value()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"key1": "value1"}), doubleDataPoints.At(1).LabelsMap()) + + // Check histogram metric + metricHistogram := metrics.At(2) + assert.EqualValues(t, "my_metric_histogram", metricHistogram.Name()) + assert.EqualValues(t, "My metric", metricHistogram.Description()) + assert.EqualValues(t, "ms", metricHistogram.Unit()) + assert.EqualValues(t, MetricDataTypeDoubleHistogram, metricHistogram.DataType()) + dhd := metricHistogram.DoubleHistogram() + assert.EqualValues(t, AggregationTemporalityDelta, dhd.AggregationTemporality()) + histogramDataPoints := dhd.DataPoints() + assert.EqualValues(t, 2, histogramDataPoints.Len()) + // First point + assert.EqualValues(t, startTime, histogramDataPoints.At(0).StartTime()) + assert.EqualValues(t, endTime, histogramDataPoints.At(0).Timestamp()) + assert.EqualValues(t, []float64{1, 2}, histogramDataPoints.At(0).ExplicitBounds()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"key0": "value0"}), histogramDataPoints.At(0).LabelsMap()) + assert.EqualValues(t, []uint64{10, 15, 1}, histogramDataPoints.At(0).BucketCounts()) + // Second point + assert.EqualValues(t, startTime, histogramDataPoints.At(1).StartTime()) + assert.EqualValues(t, endTime, histogramDataPoints.At(1).Timestamp()) + assert.EqualValues(t, []float64{1}, histogramDataPoints.At(1).ExplicitBounds()) + assert.EqualValues(t, NewStringMap().InitFromMap(map[string]string{"key1": "value1"}), histogramDataPoints.At(1).LabelsMap()) + assert.EqualValues(t, []uint64{10, 1}, histogramDataPoints.At(1).BucketCounts()) +} + +func TestOtlpToFromInternalReadOnly(t *testing.T) { + metricData := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric(), generateTestProtoDoubleSumMetric(), generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + }) + // Test that nothing changed + assert.EqualValues(t, []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric(), generateTestProtoDoubleSumMetric(), generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + }, MetricsToOtlp(metricData)) +} + +func TestOtlpToFromInternalIntGaugeMutating(t *testing.T) { + newLabels := NewStringMap().InitFromMap(map[string]string{"k": "v"}) + + metricData := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric()}, + }, + }, + }, + }) + resourceMetrics := metricData.ResourceMetrics() + metric := resourceMetrics.At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + // Mutate MetricDescriptor + metric.SetName("new_my_metric_int") + assert.EqualValues(t, "new_my_metric_int", metric.Name()) + metric.SetDescription("My new metric") + assert.EqualValues(t, "My new metric", metric.Description()) + metric.SetUnit("1") + assert.EqualValues(t, "1", metric.Unit()) + // Mutate DataPoints + igd := metric.IntGauge() + assert.EqualValues(t, 2, igd.DataPoints().Len()) + igd.DataPoints().Resize(1) + assert.EqualValues(t, 1, igd.DataPoints().Len()) + int64DataPoints := igd.DataPoints() + int64DataPoints.At(0).SetStartTime(TimestampUnixNano(startTime + 1)) + assert.EqualValues(t, startTime+1, int64DataPoints.At(0).StartTime()) + int64DataPoints.At(0).SetTimestamp(TimestampUnixNano(endTime + 1)) + assert.EqualValues(t, endTime+1, int64DataPoints.At(0).Timestamp()) + int64DataPoints.At(0).SetValue(124) + assert.EqualValues(t, 124, int64DataPoints.At(0).Value()) + int64DataPoints.At(0).LabelsMap().Delete("key0") + int64DataPoints.At(0).LabelsMap().Upsert("k", "v") + assert.EqualValues(t, newLabels, int64DataPoints.At(0).LabelsMap()) + + // Test that everything is updated. + assert.EqualValues(t, []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{ + { + Name: "new_my_metric_int", + Description: "My new metric", + Unit: "1", + Data: &otlpmetrics.Metric_IntGauge{ + IntGauge: &otlpmetrics.IntGauge{ + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "k", + Value: "v", + }, + }, + StartTimeUnixNano: startTime + 1, + TimeUnixNano: endTime + 1, + Value: 124, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, MetricsToOtlp(metricData)) +} + +func TestOtlpToFromInternalDoubleSumMutating(t *testing.T) { + newLabels := NewStringMap().InitFromMap(map[string]string{"k": "v"}) + + metricData := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoDoubleSumMetric()}, + }, + }, + }, + }) + resourceMetrics := metricData.ResourceMetrics() + metric := resourceMetrics.At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + // Mutate MetricDescriptor + metric.SetName("new_my_metric_double") + assert.EqualValues(t, "new_my_metric_double", metric.Name()) + metric.SetDescription("My new metric") + assert.EqualValues(t, "My new metric", metric.Description()) + metric.SetUnit("1") + assert.EqualValues(t, "1", metric.Unit()) + // Mutate DataPoints + dsd := metric.DoubleSum() + assert.EqualValues(t, 2, dsd.DataPoints().Len()) + dsd.DataPoints().Resize(1) + assert.EqualValues(t, 1, dsd.DataPoints().Len()) + doubleDataPoints := dsd.DataPoints() + doubleDataPoints.At(0).SetStartTime(TimestampUnixNano(startTime + 1)) + assert.EqualValues(t, startTime+1, doubleDataPoints.At(0).StartTime()) + doubleDataPoints.At(0).SetTimestamp(TimestampUnixNano(endTime + 1)) + assert.EqualValues(t, endTime+1, doubleDataPoints.At(0).Timestamp()) + doubleDataPoints.At(0).SetValue(124.1) + assert.EqualValues(t, 124.1, doubleDataPoints.At(0).Value()) + doubleDataPoints.At(0).LabelsMap().Delete("key0") + doubleDataPoints.At(0).LabelsMap().Upsert("k", "v") + assert.EqualValues(t, newLabels, doubleDataPoints.At(0).LabelsMap()) + + // Test that everything is updated. + assert.EqualValues(t, []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{ + { + Name: "new_my_metric_double", + Description: "My new metric", + Unit: "1", + Data: &otlpmetrics.Metric_DoubleSum{ + DoubleSum: &otlpmetrics.DoubleSum{ + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + DataPoints: []*otlpmetrics.DoubleDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "k", + Value: "v", + }, + }, + StartTimeUnixNano: startTime + 1, + TimeUnixNano: endTime + 1, + Value: 124.1, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, MetricsToOtlp(metricData)) +} + +func TestOtlpToFromInternalHistogramMutating(t *testing.T) { + newLabels := NewStringMap().InitFromMap(map[string]string{"k": "v"}) + + metricData := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + }) + resourceMetrics := metricData.ResourceMetrics() + metric := resourceMetrics.At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + // Mutate MetricDescriptor + metric.SetName("new_my_metric_histogram") + assert.EqualValues(t, "new_my_metric_histogram", metric.Name()) + metric.SetDescription("My new metric") + assert.EqualValues(t, "My new metric", metric.Description()) + metric.SetUnit("1") + assert.EqualValues(t, "1", metric.Unit()) + // Mutate DataPoints + dhd := metric.DoubleHistogram() + assert.EqualValues(t, 2, dhd.DataPoints().Len()) + dhd.DataPoints().Resize(1) + assert.EqualValues(t, 1, dhd.DataPoints().Len()) + histogramDataPoints := dhd.DataPoints() + histogramDataPoints.At(0).SetStartTime(TimestampUnixNano(startTime + 1)) + assert.EqualValues(t, startTime+1, histogramDataPoints.At(0).StartTime()) + histogramDataPoints.At(0).SetTimestamp(TimestampUnixNano(endTime + 1)) + assert.EqualValues(t, endTime+1, histogramDataPoints.At(0).Timestamp()) + histogramDataPoints.At(0).LabelsMap().Delete("key0") + histogramDataPoints.At(0).LabelsMap().Upsert("k", "v") + assert.EqualValues(t, newLabels, histogramDataPoints.At(0).LabelsMap()) + histogramDataPoints.At(0).SetExplicitBounds([]float64{1}) + assert.EqualValues(t, []float64{1}, histogramDataPoints.At(0).ExplicitBounds()) + histogramDataPoints.At(0).SetBucketCounts([]uint64{21, 32}) + // Test that everything is updated. + assert.EqualValues(t, []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{ + { + Name: "new_my_metric_histogram", + Description: "My new metric", + Unit: "1", + Data: &otlpmetrics.Metric_DoubleHistogram{ + DoubleHistogram: &otlpmetrics.DoubleHistogram{ + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA, + DataPoints: []*otlpmetrics.DoubleHistogramDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "k", + Value: "v", + }, + }, + StartTimeUnixNano: startTime + 1, + TimeUnixNano: endTime + 1, + BucketCounts: []uint64{21, 32}, + ExplicitBounds: []float64{1}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, MetricsToOtlp(metricData)) +} + +func TestMetricsToFromOtlpProtoBytes(t *testing.T) { + send := NewMetrics() + fillTestResourceMetricsSlice(send.ResourceMetrics()) + bytes, err := send.ToOtlpProtoBytes() + assert.NoError(t, err) + + recv := NewMetrics() + err = recv.FromOtlpProtoBytes(bytes) + assert.NoError(t, err) + assert.EqualValues(t, send, recv) +} + +func TestMetricsFromInvalidOtlpProtoBytes(t *testing.T) { + err := NewMetrics().FromOtlpProtoBytes([]byte{0xFF}) + assert.EqualError(t, err, "unexpected EOF") +} + +func TestMetricsClone(t *testing.T) { + metrics := NewMetrics() + fillTestResourceMetricsSlice(metrics.ResourceMetrics()) + assert.EqualValues(t, metrics, metrics.Clone()) +} + +func BenchmarkMetricsClone(b *testing.B) { + metrics := NewMetrics() + fillTestResourceMetricsSlice(metrics.ResourceMetrics()) + b.ResetTimer() + for n := 0; n < b.N; n++ { + clone := metrics.Clone() + if clone.ResourceMetrics().Len() != metrics.ResourceMetrics().Len() { + b.Fail() + } + } +} + +func BenchmarkOtlpToFromInternal_PassThrough(b *testing.B) { + resourceMetricsList := []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric(), generateTestProtoDoubleSumMetric(), generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + md := MetricsFromOtlp(resourceMetricsList) + MetricsToOtlp(md) + } +} + +func BenchmarkOtlpToFromInternal_IntGauge_MutateOneLabel(b *testing.B) { + resourceMetricsList := []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric()}, + }, + }, + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + md := MetricsFromOtlp(resourceMetricsList) + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).IntGauge().DataPoints().At(0).LabelsMap().Upsert("key0", "value2") + MetricsToOtlp(md) + } +} + +func BenchmarkOtlpToFromInternal_DoubleSum_MutateOneLabel(b *testing.B) { + resourceMetricsList := []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoDoubleSumMetric()}, + }, + }, + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + md := MetricsFromOtlp(resourceMetricsList) + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).DoubleSum().DataPoints().At(0).LabelsMap().Upsert("key0", "value2") + MetricsToOtlp(md) + } +} + +func BenchmarkOtlpToFromInternal_HistogramPoints_MutateOneLabel(b *testing.B) { + resourceMetricsList := []*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + md := MetricsFromOtlp(resourceMetricsList) + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).DoubleHistogram().DataPoints().At(0).LabelsMap().Upsert("key0", "value2") + MetricsToOtlp(md) + } +} + +func BenchmarkMetrics_ToOtlpProtoBytes_PassThrough(b *testing.B) { + metrics := MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{ + { + Resource: generateTestProtoResource(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + InstrumentationLibrary: generateTestProtoInstrumentationLibrary(), + Metrics: []*otlpmetrics.Metric{generateTestProtoIntGaugeMetric(), generateTestProtoDoubleSumMetric(), generateTestProtoDoubleHistogramMetric()}, + }, + }, + }, + }) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, _ = metrics.ToOtlpProtoBytes() + } +} + +func BenchmarkMetricsToOtlp(b *testing.B) { + traces := NewMetrics() + fillTestResourceMetricsSlice(traces.ResourceMetrics()) + b.ResetTimer() + for n := 0; n < b.N; n++ { + buf, err := traces.ToOtlpProtoBytes() + require.NoError(b, err) + assert.NotEqual(b, 0, len(buf)) + } +} + +func BenchmarkMetricsFromOtlp(b *testing.B) { + baseMetrics := NewMetrics() + fillTestResourceMetricsSlice(baseMetrics.ResourceMetrics()) + buf, err := baseMetrics.ToOtlpProtoBytes() + require.NoError(b, err) + assert.NotEqual(b, 0, len(buf)) + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + traces := NewMetrics() + require.NoError(b, traces.FromOtlpProtoBytes(buf)) + assert.Equal(b, baseMetrics.ResourceMetrics().Len(), traces.ResourceMetrics().Len()) + } +} + +func generateTestProtoResource() otlpresource.Resource { + return otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "string", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "string-resource"}}, + }, + }, + } +} + +func generateTestProtoInstrumentationLibrary() otlpcommon.InstrumentationLibrary { + return otlpcommon.InstrumentationLibrary{ + Name: "test", + Version: "", + } +} + +func generateTestProtoIntGaugeMetric() *otlpmetrics.Metric { + return &otlpmetrics.Metric{ + Name: "my_metric_int", + Description: "My metric", + Unit: "ms", + Data: &otlpmetrics.Metric_IntGauge{ + IntGauge: &otlpmetrics.IntGauge{ + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key0", + Value: "value0", + }, + }, + StartTimeUnixNano: startTime, + TimeUnixNano: endTime, + Value: 123, + }, + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key1", + Value: "value1", + }, + }, + StartTimeUnixNano: startTime, + TimeUnixNano: endTime, + Value: 456, + }, + }, + }, + }, + } +} +func generateTestProtoDoubleSumMetric() *otlpmetrics.Metric { + return &otlpmetrics.Metric{ + Name: "my_metric_double", + Description: "My metric", + Unit: "ms", + Data: &otlpmetrics.Metric_DoubleSum{ + DoubleSum: &otlpmetrics.DoubleSum{ + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + DataPoints: []*otlpmetrics.DoubleDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key0", + Value: "value0", + }, + }, + StartTimeUnixNano: startTime, + TimeUnixNano: endTime, + Value: 123.1, + }, + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key1", + Value: "value1", + }, + }, + StartTimeUnixNano: startTime, + TimeUnixNano: endTime, + Value: 456.1, + }, + }, + }, + }, + } +} + +func generateTestProtoDoubleHistogramMetric() *otlpmetrics.Metric { + return &otlpmetrics.Metric{ + Name: "my_metric_histogram", + Description: "My metric", + Unit: "ms", + Data: &otlpmetrics.Metric_DoubleHistogram{ + DoubleHistogram: &otlpmetrics.DoubleHistogram{ + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA, + DataPoints: []*otlpmetrics.DoubleHistogramDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key0", + Value: "value0", + }, + }, + StartTimeUnixNano: startTime, + TimeUnixNano: endTime, + BucketCounts: []uint64{10, 15, 1}, + ExplicitBounds: []float64{1, 2}, + }, + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key1", + Value: "value1", + }, + }, + StartTimeUnixNano: startTime, + TimeUnixNano: endTime, + BucketCounts: []uint64{10, 1}, + ExplicitBounds: []float64{1}, + }, + }, + }, + }, + } +} diff --git a/internal/otel_collector/consumer/pdata/spanid.go b/internal/otel_collector/consumer/pdata/spanid.go new file mode 100644 index 00000000000..52a35a1bfdd --- /dev/null +++ b/internal/otel_collector/consumer/pdata/spanid.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "go.opentelemetry.io/collector/internal/data" +) + +// SpanID is an alias of OTLP SpanID data type. +type SpanID data.SpanID + +func InvalidSpanID() SpanID { + return SpanID(data.NewSpanID([8]byte{})) +} + +func NewSpanID(bytes [8]byte) SpanID { + return SpanID(data.NewSpanID(bytes)) +} + +// Bytes returns the byte array representation of the SpanID. +func (t SpanID) Bytes() [8]byte { + return data.SpanID(t).Bytes() +} + +// HexString returns hex representation of the SpanID. +func (t SpanID) HexString() string { + return data.SpanID(t).HexString() +} + +// IsValid returns true if id contains at leas one non-zero byte. +func (t SpanID) IsValid() bool { + return data.SpanID(t).IsValid() +} diff --git a/internal/otel_collector/consumer/pdata/timestamp.go b/internal/otel_collector/consumer/pdata/timestamp.go new file mode 100644 index 00000000000..6178ae55165 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/timestamp.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "time" + + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TimestampToUnixNano(ts *timestamppb.Timestamp) (t TimestampUnixNano) { + if ts == nil { + return + } + return TimestampUnixNano(uint64(ts.AsTime().UnixNano())) +} + +func UnixNanoToTimestamp(u TimestampUnixNano) *timestamppb.Timestamp { + // 0 is a special case and want to make sure we return nil. + if u == 0 { + return nil + } + return timestamppb.New(UnixNanoToTime(u)) +} + +func UnixNanoToTime(u TimestampUnixNano) time.Time { + // 0 is a special case and want to make sure we return a time that IsZero() returns true. + if u == 0 { + return time.Time{} + } + return time.Unix(0, int64(u)).UTC() +} + +func TimeToUnixNano(t time.Time) TimestampUnixNano { + // 0 is a special case and want to make sure we return zero timestamp to support inverse function for UnixNanoToTime + if t.IsZero() { + return 0 + } + return TimestampUnixNano(uint64(t.UnixNano())) +} diff --git a/internal/otel_collector/consumer/pdata/timestamp_test.go b/internal/otel_collector/consumer/pdata/timestamp_test.go new file mode 100644 index 00000000000..eb868b677eb --- /dev/null +++ b/internal/otel_collector/consumer/pdata/timestamp_test.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestUnixNanosConverters(t *testing.T) { + t1 := time.Date(2020, 03, 24, 1, 13, 23, 789, time.UTC) + tun := TimestampUnixNano(t1.UnixNano()) + + assert.EqualValues(t, uint64(1585012403000000789), tun) + tp := UnixNanoToTimestamp(tun) + assert.EqualValues(t, ×tamppb.Timestamp{Seconds: 1585012403, Nanos: 789}, tp) + assert.EqualValues(t, tun, TimestampToUnixNano(tp)) + assert.EqualValues(t, tun, TimeToUnixNano(t1)) + assert.EqualValues(t, t1, UnixNanoToTime(TimeToUnixNano(t1))) +} + +func TestZeroTimestamps(t *testing.T) { + assert.Zero(t, TimestampToUnixNano(nil)) + assert.Nil(t, UnixNanoToTimestamp(0)) + assert.True(t, UnixNanoToTime(0).IsZero()) + assert.Zero(t, TimeToUnixNano(time.Time{})) +} diff --git a/internal/otel_collector/consumer/pdata/trace.go b/internal/otel_collector/consumer/pdata/trace.go new file mode 100644 index 00000000000..0ccb4078749 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/trace.go @@ -0,0 +1,188 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + otlpcollectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +// This file defines in-memory data structures to represent traces (spans). + +// Traces is the top-level struct that is propagated through the traces pipeline. +// This is the newer version of consumerdata.Traces, but uses more efficient +// in-memory representation. +type Traces struct { + orig *[]*otlptrace.ResourceSpans +} + +// NewTraces creates a new Traces. +func NewTraces() Traces { + orig := []*otlptrace.ResourceSpans(nil) + return Traces{&orig} +} + +// TracesFromOtlp creates the internal Traces representation from the OTLP. +func TracesFromOtlp(orig []*otlptrace.ResourceSpans) Traces { + return Traces{&orig} +} + +// TracesToOtlp converts the internal Traces to the OTLP. +func TracesToOtlp(td Traces) []*otlptrace.ResourceSpans { + return *td.orig +} + +// ToOtlpProtoBytes converts the internal Traces to OTLP Collector +// ExportTraceServiceRequest ProtoBuf bytes. +func (td Traces) ToOtlpProtoBytes() ([]byte, error) { + traces := otlpcollectortrace.ExportTraceServiceRequest{ + ResourceSpans: *td.orig, + } + return traces.Marshal() +} + +// FromOtlpProtoBytes converts OTLP Collector ExportTraceServiceRequest +// ProtoBuf bytes to the internal Traces. Overrides current data. +// Calling this function on zero-initialized structure causes panic. +// Use it with NewTraces or on existing initialized Traces. +func (td Traces) FromOtlpProtoBytes(data []byte) error { + traces := otlpcollectortrace.ExportTraceServiceRequest{} + if err := traces.Unmarshal(data); err != nil { + return err + } + *td.orig = traces.ResourceSpans + return nil +} + +// Clone returns a copy of Traces. +func (td Traces) Clone() Traces { + rss := NewResourceSpansSlice() + td.ResourceSpans().CopyTo(rss) + return Traces(rss) +} + +// SpanCount calculates the total number of spans. +func (td Traces) SpanCount() int { + spanCount := 0 + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + ilss := rs.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + spanCount += ilss.At(j).Spans().Len() + } + } + return spanCount +} + +// Size returns size in bytes. +func (td Traces) Size() int { + size := 0 + for i := 0; i < len(*td.orig); i++ { + if (*td.orig)[i] == nil { + continue + } + size += (*td.orig)[i].Size() + } + return size +} + +func (td Traces) ResourceSpans() ResourceSpansSlice { + return newResourceSpansSlice(td.orig) +} + +// TraceState in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header +type TraceState string + +type SpanKind otlptrace.Span_SpanKind + +func (sk SpanKind) String() string { return otlptrace.Span_SpanKind(sk).String() } + +const ( + TraceStateEmpty TraceState = "" +) + +const ( + SpanKindUNSPECIFIED = SpanKind(0) + SpanKindINTERNAL = SpanKind(otlptrace.Span_SPAN_KIND_INTERNAL) + SpanKindSERVER = SpanKind(otlptrace.Span_SPAN_KIND_SERVER) + SpanKindCLIENT = SpanKind(otlptrace.Span_SPAN_KIND_CLIENT) + SpanKindPRODUCER = SpanKind(otlptrace.Span_SPAN_KIND_PRODUCER) + SpanKindCONSUMER = SpanKind(otlptrace.Span_SPAN_KIND_CONSUMER) +) + +// DeprecatedStatusCode is the deprecated status code used previously. +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#set-status +// Deprecated: use StatusCode instead. +type DeprecatedStatusCode otlptrace.Status_DeprecatedStatusCode + +const ( + DeprecatedStatusCodeOk = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_OK) + DeprecatedStatusCodeCancelled = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_CANCELLED) + DeprecatedStatusCodeUnknownError = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_UNKNOWN_ERROR) + DeprecatedStatusCodeInvalidArgument = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_INVALID_ARGUMENT) + DeprecatedStatusCodeDeadlineExceeded = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_DEADLINE_EXCEEDED) + DeprecatedStatusCodeNotFound = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_NOT_FOUND) + DeprecatedStatusCodeAlreadyExists = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_ALREADY_EXISTS) + DeprecatedStatusCodePermissionDenied = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_PERMISSION_DENIED) + DeprecatedStatusCodeResourceExhausted = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_RESOURCE_EXHAUSTED) + DeprecatedStatusCodeFailedPrecondition = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_FAILED_PRECONDITION) + DeprecatedStatusCodeAborted = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_ABORTED) + DeprecatedStatusCodeOutOfRange = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_OUT_OF_RANGE) + DeprecatedStatusCodeUnimplemented = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_UNIMPLEMENTED) + DeprecatedStatusCodeInternalError = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_INTERNAL_ERROR) + DeprecatedStatusCodeUnavailable = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_UNAVAILABLE) + DeprecatedStatusCodeDataLoss = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_DATA_LOSS) + DeprecatedStatusCodeUnauthenticated = DeprecatedStatusCode(otlptrace.Status_DEPRECATED_STATUS_CODE_UNAUTHENTICATED) +) + +func (sc DeprecatedStatusCode) String() string { + return otlptrace.Status_DeprecatedStatusCode(sc).String() +} + +// StatusCode mirrors the codes defined at +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#set-status +type StatusCode otlptrace.Status_StatusCode + +const ( + StatusCodeUnset = StatusCode(otlptrace.Status_STATUS_CODE_UNSET) + StatusCodeOk = StatusCode(otlptrace.Status_STATUS_CODE_OK) + StatusCodeError = StatusCode(otlptrace.Status_STATUS_CODE_ERROR) +) + +func (sc StatusCode) String() string { return otlptrace.Status_StatusCode(sc).String() } + +// SetCode replaces the code associated with this SpanStatus. +func (ms SpanStatus) SetCode(v StatusCode) { + ms.orig.Code = otlptrace.Status_StatusCode(v) + + // According to OTLP spec we also need to set the deprecated_code field. + // See https://github.com/open-telemetry/opentelemetry-proto/blob/59c488bfb8fb6d0458ad6425758b70259ff4a2bd/opentelemetry/proto/trace/v1/trace.proto#L231 + // + // if code==STATUS_CODE_UNSET then `deprecated_code` MUST be + // set to DEPRECATED_STATUS_CODE_OK. + // + // if code==STATUS_CODE_OK then `deprecated_code` MUST be + // set to DEPRECATED_STATUS_CODE_OK. + // + // if code==STATUS_CODE_ERROR then `deprecated_code` MUST be + // set to DEPRECATED_STATUS_CODE_UNKNOWN_ERROR. + switch v { + case StatusCodeUnset, StatusCodeOk: + ms.SetDeprecatedCode(DeprecatedStatusCodeOk) + case StatusCodeError: + ms.SetDeprecatedCode(DeprecatedStatusCodeUnknownError) + } +} diff --git a/internal/otel_collector/consumer/pdata/trace_test.go b/internal/otel_collector/consumer/pdata/trace_test.go new file mode 100644 index 00000000000..a404f2cda3e --- /dev/null +++ b/internal/otel_collector/consumer/pdata/trace_test.go @@ -0,0 +1,248 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "testing" + + gogoproto "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + goproto "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/emptypb" + + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +func TestSpanCount(t *testing.T) { + md := NewTraces() + assert.EqualValues(t, 0, md.SpanCount()) + + md.ResourceSpans().Resize(1) + assert.EqualValues(t, 0, md.SpanCount()) + + md.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + assert.EqualValues(t, 0, md.SpanCount()) + + md.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + assert.EqualValues(t, 1, md.SpanCount()) + + rms := md.ResourceSpans() + rms.Resize(3) + rms.At(0).InstrumentationLibrarySpans().Resize(1) + rms.At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + rms.At(1).InstrumentationLibrarySpans().Resize(1) + rms.At(2).InstrumentationLibrarySpans().Resize(1) + rms.At(2).InstrumentationLibrarySpans().At(0).Spans().Resize(5) + assert.EqualValues(t, 6, md.SpanCount()) +} + +func TestSize(t *testing.T) { + md := NewTraces() + assert.Equal(t, 0, md.Size()) + rms := md.ResourceSpans() + rms.Resize(1) + rms.At(0).InstrumentationLibrarySpans().Resize(1) + rms.At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + rms.At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetName("foo") + otlp := TracesToOtlp(md) + size := 0 + sizeBytes := 0 + for _, rspans := range otlp { + size += rspans.Size() + bts, err := rspans.Marshal() + require.NoError(t, err) + sizeBytes += len(bts) + } + assert.Equal(t, size, md.Size()) + assert.Equal(t, sizeBytes, md.Size()) +} + +func TestTracesSizeWithNil(t *testing.T) { + assert.Equal(t, 0, TracesFromOtlp([]*otlptrace.ResourceSpans{nil}).Size()) +} + +func TestSpanCountWithEmpty(t *testing.T) { + assert.EqualValues(t, 0, TracesFromOtlp([]*otlptrace.ResourceSpans{{}}).SpanCount()) + assert.EqualValues(t, 0, TracesFromOtlp([]*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{{}}, + }, + }).SpanCount()) + assert.EqualValues(t, 1, TracesFromOtlp([]*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{{}}, + }, + }, + }, + }).SpanCount()) +} + +func TestSpanID(t *testing.T) { + sid := InvalidSpanID() + assert.EqualValues(t, [8]byte{}, sid.Bytes()) + + sid = NewSpanID([8]byte{1, 2, 3, 4, 4, 3, 2, 1}) + assert.EqualValues(t, [8]byte{1, 2, 3, 4, 4, 3, 2, 1}, sid.Bytes()) +} + +func TestTraceID(t *testing.T) { + tid := InvalidTraceID() + assert.EqualValues(t, [16]byte{}, tid.Bytes()) + + tid = NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}) + assert.EqualValues(t, [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, tid.Bytes()) +} + +func TestSpanStatusCode(t *testing.T) { + td := NewTraces() + rss := td.ResourceSpans() + rss.Resize(1) + rss.At(0).InstrumentationLibrarySpans().Resize(1) + rss.At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + status := rss.At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).Status() + + // Check handling of deprecated status code, see spec here: + // https://github.com/open-telemetry/opentelemetry-proto/blob/59c488bfb8fb6d0458ad6425758b70259ff4a2bd/opentelemetry/proto/trace/v1/trace.proto#L231 + // + // 2. New senders, which are aware of the `code` field MUST set both the + // `deprecated_code` and `code` fields according to the following rules: + // + // if code==STATUS_CODE_UNSET then `deprecated_code` MUST be + // set to DEPRECATED_STATUS_CODE_OK. + status.SetDeprecatedCode(DeprecatedStatusCodeUnknownError) + assert.EqualValues(t, DeprecatedStatusCodeUnknownError, status.DeprecatedCode()) + status.SetCode(StatusCodeUnset) + assert.EqualValues(t, DeprecatedStatusCodeOk, status.DeprecatedCode()) + + // if code==STATUS_CODE_OK then `deprecated_code` MUST be + // set to DEPRECATED_STATUS_CODE_OK. + status.SetDeprecatedCode(DeprecatedStatusCodeUnknownError) + assert.EqualValues(t, DeprecatedStatusCodeUnknownError, status.DeprecatedCode()) + status.SetCode(StatusCodeOk) + assert.EqualValues(t, DeprecatedStatusCodeOk, status.DeprecatedCode()) + + // if code==STATUS_CODE_ERROR then `deprecated_code` MUST be + // set to DEPRECATED_STATUS_CODE_UNKNOWN_ERROR. + status.SetDeprecatedCode(DeprecatedStatusCodeOk) + assert.EqualValues(t, DeprecatedStatusCodeOk, status.DeprecatedCode()) + status.SetCode(StatusCodeError) + assert.EqualValues(t, DeprecatedStatusCodeUnknownError, status.DeprecatedCode()) +} + +func TestToFromOtlp(t *testing.T) { + otlp := []*otlptrace.ResourceSpans(nil) + td := TracesFromOtlp(otlp) + assert.EqualValues(t, NewTraces(), td) + assert.EqualValues(t, otlp, TracesToOtlp(td)) + // More tests in ./tracedata/trace_test.go. Cannot have them here because of + // circular dependency. +} + +func TestResourceSpansWireCompatibility(t *testing.T) { + // This test verifies that OTLP ProtoBufs generated using goproto lib in + // opentelemetry-proto repository OTLP ProtoBufs generated using gogoproto lib in + // this repository are wire compatible. + + // Generate ResourceSpans as pdata struct. + pdataRS := generateTestResourceSpans() + + // Marshal its underlying ProtoBuf to wire. + wire1, err := gogoproto.Marshal(pdataRS.orig) + assert.NoError(t, err) + assert.NotNil(t, wire1) + + // Unmarshal from the wire to OTLP Protobuf in goproto's representation. + var goprotoMessage emptypb.Empty + err = goproto.Unmarshal(wire1, &goprotoMessage) + assert.NoError(t, err) + + // Marshal to the wire again. + wire2, err := goproto.Marshal(&goprotoMessage) + assert.NoError(t, err) + assert.NotNil(t, wire2) + + // Unmarshal from the wire into gogoproto's representation. + var gogoprotoRS2 otlptrace.ResourceSpans + err = gogoproto.Unmarshal(wire2, &gogoprotoRS2) + assert.NoError(t, err) + + // Now compare that the original and final ProtoBuf messages are the same. + // This proves that goproto and gogoproto marshaling/unmarshaling are wire compatible. + assert.EqualValues(t, pdataRS.orig, &gogoprotoRS2) +} + +func TestTracesToFromOtlpProtoBytes(t *testing.T) { + send := NewTraces() + fillTestResourceSpansSlice(send.ResourceSpans()) + bytes, err := send.ToOtlpProtoBytes() + assert.NoError(t, err) + + recv := NewTraces() + err = recv.FromOtlpProtoBytes(bytes) + assert.NoError(t, err) + assert.EqualValues(t, send, recv) +} + +func TestTracesFromInvalidOtlpProtoBytes(t *testing.T) { + err := NewTraces().FromOtlpProtoBytes([]byte{0xFF}) + assert.EqualError(t, err, "unexpected EOF") +} + +func TestTracesClone(t *testing.T) { + traces := NewTraces() + fillTestResourceSpansSlice(traces.ResourceSpans()) + assert.EqualValues(t, traces, traces.Clone()) +} + +func BenchmarkTracesClone(b *testing.B) { + traces := NewTraces() + fillTestResourceSpansSlice(traces.ResourceSpans()) + b.ResetTimer() + for n := 0; n < b.N; n++ { + clone := traces.Clone() + if clone.ResourceSpans().Len() != traces.ResourceSpans().Len() { + b.Fail() + } + } +} + +func BenchmarkTracesToOtlp(b *testing.B) { + traces := NewTraces() + fillTestResourceSpansSlice(traces.ResourceSpans()) + b.ResetTimer() + for n := 0; n < b.N; n++ { + buf, err := traces.ToOtlpProtoBytes() + require.NoError(b, err) + assert.NotEqual(b, 0, len(buf)) + } +} + +func BenchmarkTracesFromOtlp(b *testing.B) { + baseTraces := NewTraces() + fillTestResourceSpansSlice(baseTraces.ResourceSpans()) + buf, err := baseTraces.ToOtlpProtoBytes() + require.NoError(b, err) + assert.NotEqual(b, 0, len(buf)) + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + traces := NewTraces() + require.NoError(b, traces.FromOtlpProtoBytes(buf)) + assert.Equal(b, baseTraces.ResourceSpans().Len(), traces.ResourceSpans().Len()) + } +} diff --git a/internal/otel_collector/consumer/pdata/traceid.go b/internal/otel_collector/consumer/pdata/traceid.go new file mode 100644 index 00000000000..d374499a3d0 --- /dev/null +++ b/internal/otel_collector/consumer/pdata/traceid.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pdata + +import ( + "go.opentelemetry.io/collector/internal/data" +) + +// TraceID is an alias of OTLP TraceID data type. +type TraceID data.TraceID + +func InvalidTraceID() TraceID { + return TraceID(data.NewTraceID([16]byte{})) +} + +func NewTraceID(bytes [16]byte) TraceID { + return TraceID(data.NewTraceID(bytes)) +} + +// Bytes returns the byte array representation of the TraceID. +func (t TraceID) Bytes() [16]byte { + return data.TraceID(t).Bytes() +} + +// HexString returns hex representation of the TraceID. +func (t TraceID) HexString() string { + return data.TraceID(t).HexString() +} + +// IsValid returns true if id contains at leas one non-zero byte. +func (t TraceID) IsValid() bool { + return data.TraceID(t).IsValid() +} diff --git a/internal/otel_collector/consumer/simple/metrics.go b/internal/otel_collector/consumer/simple/metrics.go new file mode 100644 index 00000000000..cd9e5a60df8 --- /dev/null +++ b/internal/otel_collector/consumer/simple/metrics.go @@ -0,0 +1,360 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package simple + +import ( + "fmt" + "sync" + "time" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Metrics facilitates building pdata.Metrics in receivers. It is meant +// to be much easier and more fluent than than using pdata.Metrics directly. +// All of the exported methods on it return the same instance of Metrics +// as a return value, allowing you to chain method calls easily, similar to the +// Java builder pattern. +// +// All of the public fields in this structure are meant to be set before the +// first data point is added, and should not be changed afterwards. +// +// The Metrics is designed for cases where receivers are generating +// metrics from scratch, where generally you will have a single datapoint per +// metric/label combination. +// +// One restriction this helper imposes is that a particular metric name must +// only be used with a single data type for all instances derived from a base +// helper, including the base instance. This restriction greatly simplifies +// the logic to reuse metrics for multiple datapoints and it is generally +// easier for backends to not have to deal with conflicting types anyway. +// +// It is NOT thread-safe, so you should use an external mutex if using it from +// multiple goroutines. +type Metrics struct { + // REQUIRED. A Metrics object that has been created with + // `pdata.NewMetrics()`. This is required to be set on the builder. All + // metrics added will go into this immediately upon invocation of Add* + // methods. Do not change this once initially set. + pdata.Metrics + + // MetricFactoriesByName is an optional map of metric factories that will + // be created with the appropriate name, description, and type field. This + // is intended to be used with the metadata code generation modules but can + // be used apart from that just as well. The returned metrics are expected + // to be initialized. + MetricFactoriesByName map[string]func() pdata.Metric + + // If set, this instrumentation library name will be used for all metrics + // generated by this builder. This is meant to be set once at builder + // creation and not changed later. + InstrumentationLibraryName string + // If set, this instrumentation library version will be used for all + // metrics generated by this builder. This is meant to be set once at + // builder creation and not changed later. + InstrumentationLibraryVersion string + // These attributes will be added to the Resource object on all + // ResourceMetrics instances created by the builder. This is meant to be + // set once at builder creation and not changed later. + ResourceAttributes map[string]string + // This time will be used as the Timestamp for all metrics generated. It + // can be updated with a new timestamp at any time. + Timestamp time.Time + // A set of labels that will be applied to all datapoints emitted by the + // builder. + Labels map[string]string + + resourceMetricIdx **int + metricIdxByName map[string]int +} + +func (mb *Metrics) ensureInit() { + if mb.metricIdxByName == nil { + mb.metricIdxByName = map[string]int{} + } + if mb.resourceMetricIdx == nil { + var ip *int + mb.resourceMetricIdx = &ip + } +} + +// Clone the MetricBuilder. All of the maps copied will be deeply copied. +func (mb *Metrics) clone() *Metrics { + mb.ensureInit() + + return &Metrics{ + Metrics: mb.Metrics, + MetricFactoriesByName: mb.MetricFactoriesByName, + InstrumentationLibraryName: mb.InstrumentationLibraryName, + InstrumentationLibraryVersion: mb.InstrumentationLibraryVersion, + ResourceAttributes: cloneStringMap(mb.ResourceAttributes), + Timestamp: mb.Timestamp, + Labels: cloneStringMap(mb.Labels), + resourceMetricIdx: mb.resourceMetricIdx, + metricIdxByName: mb.metricIdxByName, + } +} + +// WithLabels returns a new, independent builder with additional labels. These +// labels will be combined with the Labels that can be set on the struct. +// All subsequent calls to create metrics will create metrics that use these +// labels. The input map's entries are copied so the map can be mutated freely +// by the caller afterwards without affecting the builder. +func (mb *Metrics) WithLabels(l map[string]string) *Metrics { + out := mb.clone() + + for k, v := range l { + out.Labels[k] = v + } + + return out +} + +// AsSafeBuilder returns an instance of this builder wrapped in +// SafeMetrics that ensures all of the public methods on this instance +// will be thread-safe between goroutines. You must explicitly type these +// instances as SafeMetrics. +func (mb Metrics) AsSafe() *SafeMetrics { + return &SafeMetrics{ + Metrics: &mb, + Mutex: &sync.Mutex{}, + } +} + +func (mb *Metrics) AddGaugeDataPoint(name string, metricValue int64) *Metrics { + typ := pdata.MetricDataTypeIntGauge + mb.addDataPoint(name, typ, metricValue) + return mb +} + +func (mb *Metrics) AddDGaugeDataPoint(name string, metricValue float64) *Metrics { + typ := pdata.MetricDataTypeDoubleGauge + mb.addDataPoint(name, typ, metricValue) + return mb +} + +func (mb *Metrics) AddSumDataPoint(name string, metricValue int64) *Metrics { + typ := pdata.MetricDataTypeIntSum + mb.addDataPoint(name, typ, metricValue) + return mb +} + +func (mb *Metrics) AddDSumDataPoint(name string, metricValue float64) *Metrics { + typ := pdata.MetricDataTypeDoubleSum + mb.addDataPoint(name, typ, metricValue) + return mb +} + +func (mb *Metrics) AddHistogramRawDataPoint(name string, hist pdata.IntHistogramDataPoint) *Metrics { + mb.addDataPoint(name, pdata.MetricDataTypeIntHistogram, hist) + return mb +} + +func (mb *Metrics) AddDHistogramRawDataPoint(name string, hist pdata.DoubleHistogramDataPoint) *Metrics { + mb.addDataPoint(name, pdata.MetricDataTypeDoubleHistogram, hist) + return mb +} + +func (mb *Metrics) getMetricsSlice() pdata.MetricSlice { + rms := mb.Metrics.ResourceMetrics() + if mb.resourceMetricIdx != nil && *mb.resourceMetricIdx != nil { + return rms.At(**mb.resourceMetricIdx).InstrumentationLibraryMetrics().At(0).Metrics() + } + + rmsLen := rms.Len() + rms.Resize(rmsLen + 1) + rm := rms.At(rmsLen) + + res := rm.Resource() + for k, v := range mb.ResourceAttributes { + res.Attributes().Insert(k, pdata.NewAttributeValueString(v)) + } + + ilms := rm.InstrumentationLibraryMetrics() + ilms.Resize(1) + ilm := ilms.At(0) + + il := ilm.InstrumentationLibrary() + il.SetName(mb.InstrumentationLibraryName) + il.SetVersion(mb.InstrumentationLibraryVersion) + + *mb.resourceMetricIdx = &rmsLen + + return ilm.Metrics() +} + +func (mb *Metrics) getOrCreateMetric(name string, typ pdata.MetricDataType) pdata.Metric { + mb.ensureInit() + + metricSlice := mb.getMetricsSlice() + + idx, ok := mb.metricIdxByName[name] + if ok { + return metricSlice.At(idx) + } + + var metric pdata.Metric + if fac, ok := mb.MetricFactoriesByName[name]; ok { + metric = fac() + } else { + metric = pdata.NewMetric() + + metric.SetName(name) + metric.SetDataType(typ) + } + + metricSlice.Append(metric) + + mb.metricIdxByName[name] = metricSlice.Len() - 1 + return metric +} + +func (mb *Metrics) addDataPoint(name string, typ pdata.MetricDataType, val interface{}) { + metric := mb.getOrCreateMetric(name, typ) + + // This protects against reusing the same metric name with different types. + if metric.DataType() != typ { + panic(fmt.Errorf("mismatched metric data types for metric %q: %q vs %q", metric.Name(), metric.DataType(), typ)) + } + + tsNano := pdata.TimestampUnixNano(mb.Timestamp.UnixNano()) + + switch typ { + case pdata.MetricDataTypeIntGauge: + m := metric.IntGauge() + dps := m.DataPoints() + dp := pdata.NewIntDataPoint() + dp.LabelsMap().InitFromMap(mb.Labels) + dp.SetValue(val.(int64)) + dp.SetTimestamp(tsNano) + dps.Append(dp) + + case pdata.MetricDataTypeIntSum: + m := metric.IntSum() + dps := m.DataPoints() + dp := pdata.NewIntDataPoint() + dp.LabelsMap().InitFromMap(mb.Labels) + dp.SetValue(val.(int64)) + dp.SetTimestamp(tsNano) + dps.Append(dp) + + case pdata.MetricDataTypeDoubleGauge: + m := metric.DoubleGauge() + dps := m.DataPoints() + dp := pdata.NewDoubleDataPoint() + dp.LabelsMap().InitFromMap(mb.Labels) + dp.SetValue(val.(float64)) + dp.SetTimestamp(tsNano) + dps.Append(dp) + + case pdata.MetricDataTypeDoubleSum: + m := metric.DoubleSum() + dps := m.DataPoints() + dp := pdata.NewDoubleDataPoint() + dp.LabelsMap().InitFromMap(mb.Labels) + dp.SetValue(val.(float64)) + dp.SetTimestamp(tsNano) + dps.Append(dp) + + case pdata.MetricDataTypeIntHistogram: + m := metric.IntHistogram() + dps := m.DataPoints() + dp := pdata.NewIntHistogramDataPoint() + dp.LabelsMap().InitFromMap(mb.Labels) + val.(pdata.IntHistogramDataPoint).CopyTo(dp) + dp.SetTimestamp(tsNano) + dps.Append(dp) + + case pdata.MetricDataTypeDoubleHistogram: + m := metric.DoubleHistogram() + dps := m.DataPoints() + dp := pdata.NewDoubleHistogramDataPoint() + dp.LabelsMap().InitFromMap(mb.Labels) + val.(pdata.DoubleHistogramDataPoint).CopyTo(dp) + dp.SetTimestamp(tsNano) + dps.Append(dp) + + default: + panic("invalid metric type: " + typ.String()) + } +} + +func cloneStringMap(m map[string]string) map[string]string { + out := make(map[string]string, len(m)) + for k, v := range m { + out[k] = v + } + return out +} + +// SafeMetrics is a wrapper for Metrics that ensures the wrapped +// instance can be used safely across goroutines. It is meant to be created +// from the AsSafeBuilder on Metrics. +type SafeMetrics struct { + *sync.Mutex + *Metrics +} + +func (mb *SafeMetrics) WithLabels(l map[string]string) *SafeMetrics { + mb.Lock() + defer mb.Unlock() + + return &SafeMetrics{ + Metrics: mb.Metrics.WithLabels(l), + Mutex: mb.Mutex, + } +} + +func (mb *SafeMetrics) AddGaugeDataPoint(name string, metricValue int64) *SafeMetrics { + mb.Lock() + mb.Metrics.AddGaugeDataPoint(name, metricValue) + mb.Unlock() + return mb +} + +func (mb *SafeMetrics) AddDGaugeDataPoint(name string, metricValue float64) *SafeMetrics { + mb.Lock() + mb.Metrics.AddDGaugeDataPoint(name, metricValue) + mb.Unlock() + return mb +} + +func (mb *SafeMetrics) AddSumDataPoint(name string, metricValue int64) *SafeMetrics { + mb.Lock() + mb.Metrics.AddSumDataPoint(name, metricValue) + mb.Unlock() + return mb +} + +func (mb *SafeMetrics) AddDSumDataPoint(name string, metricValue float64) *SafeMetrics { + mb.Lock() + mb.Metrics.AddDSumDataPoint(name, metricValue) + mb.Unlock() + return mb +} + +func (mb *SafeMetrics) AddHistogramRawDataPoint(name string, hist pdata.IntHistogramDataPoint) *SafeMetrics { + mb.Lock() + mb.Metrics.AddHistogramRawDataPoint(name, hist) + mb.Unlock() + return mb +} + +func (mb *SafeMetrics) AddDHistogramRawDataPoint(name string, hist pdata.DoubleHistogramDataPoint) *SafeMetrics { + mb.Lock() + mb.Metrics.AddDHistogramRawDataPoint(name, hist) + mb.Unlock() + return mb +} diff --git a/internal/otel_collector/consumer/simple/metrics_test.go b/internal/otel_collector/consumer/simple/metrics_test.go new file mode 100644 index 00000000000..412face9c90 --- /dev/null +++ b/internal/otel_collector/consumer/simple/metrics_test.go @@ -0,0 +1,516 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package simple + +import ( + "encoding/json" + "fmt" + "strconv" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testutil/metricstestutil" +) + +func ExampleMetrics() { + metrics := pdata.NewMetrics() + + mb := Metrics{ + Metrics: metrics, + InstrumentationLibraryName: "example", + InstrumentationLibraryVersion: "0.1", + ResourceAttributes: map[string]string{ + "host": "my-host", + }, + Timestamp: time.Now(), + } + + for _, disk := range []string{"sda", "sdb", "sdc"} { + // All metrics added after this will have these labels + diskBuilder := mb.WithLabels(map[string]string{ + "disk": disk, + }).AddGaugeDataPoint("disk.usage", 9000000) + + // Add metrics in a chained manner + diskBuilder. + AddGaugeDataPoint("disk.capacity", 9000000). + AddDGaugeDataPoint("disk.temp", 30.5) + + // Add additional labels + diskBuilder.WithLabels(map[string]string{ + "direction": "read", + }).AddSumDataPoint("disk.ops", 50) + + diskBuilder.WithLabels(map[string]string{ + "direction": "write", + }).AddSumDataPoint("disk.ops", 80) + } + + metricCount, dpCount := metrics.MetricAndDataPointCount() + fmt.Printf("Metrics: %d\nDataPoints: %d", metricCount, dpCount) + + // Do not reuse Metrics once you are done using the generated Metrics. Make + // a new instance of it along with a new instance of pdata.Metrics. + + // Output: + // Metrics: 4 + // DataPoints: 15 +} + +func TestMetrics(t *testing.T) { + metrics := pdata.NewMetrics() + + mb := Metrics{ + Metrics: metrics, + InstrumentationLibraryName: "example", + InstrumentationLibraryVersion: "0.1", + ResourceAttributes: map[string]string{ + "host": "my-host", + }, + Timestamp: time.Unix(0, 1597266546570840817), + Labels: map[string]string{ + "disk": "sda", + }, + } + + expected := `[ + { + "resource": { + "attributes": [ + { + "key": "host", + "value": { + "Value": { + "string_value": "my-host" + } + } + } + ] + }, + "instrumentation_library_metrics": [ + { + "instrumentation_library": { + "name": "example", + "version": "0.1" + }, + "metrics": [ + { + "name": "disk.capacity", + "Data": { + "int_gauge": { + "data_points": [ + { + "labels": [ + { + "key": "disk", + "value": "sda" + } + ], + "time_unix_nano": 1597266546570840817, + "value": 9000000 + } + ] + } + } + }, + { + "name": "disk.reads", + "Data": { + "int_sum": { + "data_points": [ + { + "labels": [ + { + "key": "disk", + "value": "sda" + } + ], + "time_unix_nano": 1597266546570840817, + "value": 50 + }, + { + "labels": [ + { + "key": "disk", + "value": "sda" + }, + { + "key": "partition", + "value": "1" + } + ], + "time_unix_nano": 1597266546570840817, + "value": 5 + } + ] + } + } + }, + { + "name": "disk.temp", + "Data": { + "double_gauge": { + "data_points": [ + { + "labels": [ + { + "key": "disk", + "value": "sda" + } + ], + "time_unix_nano": 1597266546570840817, + "value": 30.5 + } + ] + } + } + }, + { + "name": "disk.time_awake", + "Data": { + "double_sum": { + "data_points": [ + { + "labels": [ + { + "key": "disk", + "value": "sda" + } + ], + "time_unix_nano": 1597266546570840817, + "value": 100.6 + } + ] + } + } + }, + { + "name": "partition.capacity", + "Data": { + "int_gauge": { + "data_points": [ + { + "labels": [ + { + "key": "disk", + "value": "sda" + }, + { + "key": "partition", + "value": "1" + } + ], + "time_unix_nano": 1597266546570840817, + "value": 40000 + } + ] + } + } + }, + { + "name": "disk.times", + "Data": { + "int_histogram": { + "data_points": [ + { + "labels": [], + "time_unix_nano": 1597266546570840817 + } + ] + } + } + }, + { + "name": "disk.double_times", + "Data": { + "double_histogram": { + "data_points": [ + { + "labels": [], + "time_unix_nano": 1597266546570840817 + } + ] + } + } + } + ] + } + ] + } +]` + + mb. + AddGaugeDataPoint("disk.capacity", 9000000). + AddSumDataPoint("disk.reads", 50). + AddDGaugeDataPoint("disk.temp", 30.5). + AddDSumDataPoint("disk.time_awake", 100.6) + + intHisto := pdata.NewIntHistogramDataPoint() + doubleHisto := pdata.NewDoubleHistogramDataPoint() + + mb.WithLabels(map[string]string{ + "partition": "1", + }). + AddGaugeDataPoint("partition.capacity", 40000). + AddSumDataPoint("disk.reads", 5). + AddHistogramRawDataPoint("disk.times", intHisto). + AddDHistogramRawDataPoint("disk.double_times", doubleHisto) + + mCount, dpCount := metrics.MetricAndDataPointCount() + require.Equal(t, 7, mCount) + require.Equal(t, 8, dpCount) + asJSON, _ := json.MarshalIndent(pdata.MetricsToOtlp(metricstestutil.SortedMetrics(metrics)), "", " ") + require.Equal(t, expected, string(asJSON)) +} + +func TestMetricFactories(t *testing.T) { + mb := Metrics{ + Metrics: pdata.NewMetrics(), + MetricFactoriesByName: map[string]func() pdata.Metric{ + "disk.ops": func() pdata.Metric { + m := pdata.NewMetric() + m.SetName("disk.ops") + m.SetDescription("This counts disk operations") + m.SetDataType(pdata.MetricDataTypeIntSum) + return m + }, + }, + InstrumentationLibraryName: "example", + InstrumentationLibraryVersion: "0.1", + ResourceAttributes: map[string]string{ + "host": "my-host", + }, + Timestamp: time.Unix(0, 1597266546570840817), + Labels: map[string]string{ + "disk": "sda", + }, + } + + mb.WithLabels(map[string]string{ + "direction": "read", + }).AddSumDataPoint("disk.ops", 5) + + mb.WithLabels(map[string]string{ + "direction": "write", + }).AddSumDataPoint("disk.ops", 5) + + rms := mb.Metrics.ResourceMetrics() + require.Equal(t, 1, rms.Len()) + + ilms := rms.At(0).InstrumentationLibraryMetrics() + require.Equal(t, 1, ilms.Len()) + require.Equal(t, 1, ilms.At(0).Metrics().Len()) + m := ilms.At(0).Metrics().At(0) + require.Equal(t, "disk.ops", m.Name()) + require.Equal(t, "This counts disk operations", m.Description()) + require.Equal(t, pdata.MetricDataTypeIntSum, m.DataType()) + require.Equal(t, 2, m.IntSum().DataPoints().Len()) + + require.PanicsWithError(t, `mismatched metric data types for metric "disk.ops": "IntSum" vs "IntGauge"`, func() { + mb.AddGaugeDataPoint("disk.ops", 1) + }) + + mb.AddGaugeDataPoint("disk.temp", 25) + require.Equal(t, 2, ilms.At(0).Metrics().Len()) + m = ilms.At(0).Metrics().At(1) + require.Equal(t, "disk.temp", m.Name()) + require.Equal(t, "", m.Description()) + require.Equal(t, pdata.MetricDataTypeIntGauge, m.DataType()) + require.Equal(t, 1, m.IntGauge().DataPoints().Len()) +} + +func ExampleSafeMetrics() { + metrics := pdata.NewMetrics() + + mb := Metrics{ + Metrics: metrics, + InstrumentationLibraryName: "example", + InstrumentationLibraryVersion: "0.1", + ResourceAttributes: map[string]string{ + "host": "my-host", + }, + Timestamp: time.Now(), + }.AsSafe() + + var wg sync.WaitGroup + for _, disk := range []string{"sda", "sdb", "sdc"} { + wg.Add(1) + go func(disk string) { + // All metrics added after this will have these labels + diskBuilder := mb.WithLabels(map[string]string{ + "disk": disk, + }).AddGaugeDataPoint("disk.usage", 9000000) + + // Add metrics in a chained manner + diskBuilder. + AddGaugeDataPoint("disk.capacity", 9000000). + AddSumDataPoint("disk.reads", 50) + + // Or add them on their own + diskBuilder.AddDGaugeDataPoint("disk.temp", 30.5) + + wg.Done() + }(disk) + } + wg.Wait() + + metricCount, dpCount := metrics.MetricAndDataPointCount() + fmt.Printf("Metrics: %d\nDataPoints: %d", metricCount, dpCount) + + // Output: + // Metrics: 4 + // DataPoints: 12 +} + +func TestSafeMetrics(t *testing.T) { + metrics := pdata.NewMetrics() + + mb := Metrics{ + Metrics: metrics, + InstrumentationLibraryName: "example", + InstrumentationLibraryVersion: "0.1", + ResourceAttributes: map[string]string{ + "host": "my-host", + }, + Timestamp: time.Unix(0, 1597266546570840817), + Labels: map[string]string{ + "disk": "sda", + }, + }.AsSafe() + + ready := make(chan struct{}) + var wg sync.WaitGroup + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func(idx string) { + <-ready + mb. + AddGaugeDataPoint("disk.capacity"+idx, 9000000). + AddSumDataPoint("disk.reads"+idx, 50). + AddDGaugeDataPoint("disk.temp"+idx, 30.5). + AddDSumDataPoint("disk.time_awake"+idx, 100.6) + + intHisto := pdata.NewIntHistogramDataPoint() + doubleHisto := pdata.NewDoubleHistogramDataPoint() + + for j := 0; j < 5; j++ { + mb.WithLabels(map[string]string{ + "partition": strconv.Itoa(j), + }). + AddGaugeDataPoint("partition.capacity", 40000). + AddSumDataPoint("disk.reads", 5). + AddHistogramRawDataPoint("disk.times", intHisto). + AddDHistogramRawDataPoint("disk.double_times", doubleHisto) + } + wg.Done() + }(strconv.Itoa(i)) + } + + close(ready) + wg.Wait() + + mCount, dpCount := metrics.MetricAndDataPointCount() + require.Equal(t, 4004, mCount) + require.Equal(t, 24000, dpCount) +} + +func BenchmarkSimpleMetrics(b *testing.B) { + for n := 0; n < b.N; n++ { + mb := Metrics{ + Metrics: pdata.NewMetrics(), + InstrumentationLibraryName: "example", + InstrumentationLibraryVersion: "0.1", + ResourceAttributes: map[string]string{ + "host": "my-host", + "service": "app", + }, + Timestamp: time.Now(), + Labels: map[string]string{ + "env": "prod", + "app": "myapp", + "version": "1.0", + }, + } + + for i := 0; i < 50; i++ { + name := "gauge" + strconv.Itoa(i) + mb.AddGaugeDataPoint(name, 5) + mb.AddGaugeDataPoint(name, 5) + } + } +} + +func BenchmarkPdataMetrics(b *testing.B) { + for n := 0; n < b.N; n++ { + tsNano := pdata.TimestampUnixNano(time.Now().UnixNano()) + + m := pdata.NewMetrics() + + rms := m.ResourceMetrics() + + rmsLen := rms.Len() + rms.Resize(rmsLen + 1) + rm := rms.At(rmsLen) + + res := rm.Resource() + resAttrs := res.Attributes() + resAttrs.Insert("host", pdata.NewAttributeValueString("my-host")) + resAttrs.Insert("serviceName", pdata.NewAttributeValueString("app")) + + ilms := rm.InstrumentationLibraryMetrics() + ilms.Resize(1) + ilm := ilms.At(0) + metrics := ilm.Metrics() + metrics.Resize(6) + + il := ilm.InstrumentationLibrary() + il.SetName("example") + il.SetVersion("0.1") + + for i := 0; i < 50; i++ { + metric := metrics.At(0) + metric.SetName("gauge" + strconv.Itoa(i)) + metric.SetDataType(pdata.MetricDataTypeIntGauge) + mAsType := metric.IntGauge() + dps := mAsType.DataPoints() + dps.Resize(2) + { + dp := dps.At(0) + labels := dp.LabelsMap() + labels.InitEmptyWithCapacity(3) + labels.Insert("env", "prod") + labels.Insert("app", "myapp") + labels.Insert("version", "1.0") + dp.SetValue(5) + dp.SetTimestamp(tsNano) + } + { + dp := dps.At(1) + labels := dp.LabelsMap() + labels.InitEmptyWithCapacity(3) + labels.Insert("env", "prod") + labels.Insert("app", "myapp") + labels.Insert("version", "1.0") + dp.SetValue(5) + dp.SetTimestamp(tsNano) + } + } + } +} diff --git a/internal/otel_collector/docs/design.md b/internal/otel_collector/docs/design.md new file mode 100644 index 00000000000..c5172cf8882 --- /dev/null +++ b/internal/otel_collector/docs/design.md @@ -0,0 +1,232 @@ +# OpenTelemetry Collector Architecture + +This document describes the architecture design and implementation of +OpenTelemetry Collector. + +## Summary + +OpenTelemetry Collector is an executable that allows to receive telemetry data, optionally transform it and send the data further. + +The Collector supports several popular open-source protocols for telemetry data receiving and sending as well as offering a pluggable architecture for adding more protocols. + +Data receiving, transformation and sending is done using Pipelines. The Collector can be configured to have one or more Pipelines. Each Pipeline includes a set of Receivers that receive the data, a series of optional Processors that get the data from receivers and transform it and a set of Exporters which get the data from the Processors and send it further outside the Collector. The same receiver can feed data to multiple Pipelines and multiple pipelines can feed data into the same Exporter. + +## Pipelines + +Pipeline defines a path the data follows in the Collector starting from reception, then further processing or modification and finally exiting the Collector via exporters. + +Pipelines can operate on 2 telemetry data types: traces and metrics. The data type is a property of the pipeline defined by its configuration. Receivers, exporters and processors used in a pipeline must support the particular data type otherwise `ErrDataTypeIsNotSupported` will be reported when the configuration is loaded. A pipeline can be depicted the following way: + +![Pipelines](images/design-pipelines.png) + +There can be one or more receivers in a pipeline. Data from all receivers is pushed to the first processor, which performs a processing on it and then pushes it to the next processor (or it may drop the data, e.g. if it is a “sampling” processor) and so on until the last processor in the pipeline pushes the data to the exporters. Each exporter gets a copy of each data element. The last processor uses a `FanOutConnector` to fan out the data to multiple exporters. + +The pipeline is constructed during Collector startup based on pipeline definition in the config file. + +A pipeline configuration typically looks like this: + +```yaml +service: + pipelines: # section that can contain multiple subsections, one per pipeline + traces: # type of the pipeline + receivers: [opencensus, jaeger, zipkin] + processors: [tags, tail_sampling, batch, queued_retry] + exporters: [opencensus, jaeger, stackdriver, zipkin] +``` + +The above example defines a pipeline for “traces” type of telemetry data, with 3 receivers, 4 processors and 4 exporters. + +For details of config file format see [this document](https://docs.google.com/document/d/1NeheFG7DmcUYo_h2vLtNRlia9x5wOJMlV4QKEK05FhQ/edit#). + +### Receivers + +Receivers typically listen on a network port and receive telemetry data. Usually one receiver is configured to send received data to one pipeline, however it is also possible to configure the same receiver to send the same received data to multiple pipelines. This can be done by simply listing the same receiver in the “receivers” key of several pipelines: + +```yaml +receivers: + opencensus: + endpoint: "0.0.0.0:55678" + +service: + pipelines: + traces: # a pipeline of “traces” type + receivers: [opencensus] + processors: [tags, tail_sampling, batch, queued_retry] + exporters: [jaeger] + traces/2: # another pipeline of “traces” type + receivers: [opencensus] + processors: [batch] + exporters: [opencensus] +``` + +In the above example “opencensus” receiver will send the same data to pipeline “traces” and to pipeline “traces/2”. (Note: the configuration uses composite key names in the form of `type[/name]` as defined in [this document](https://docs.google.com/document/d/1NeheFG7DmcUYo_h2vLtNRlia9x5wOJMlV4QKEK05FhQ/edit#)). + +When the Collector loads this config the result will look like this (part of processors and exporters are omitted from the diagram for brevity): + +![Receivers](images/design-receivers.png) + +Important: when the same receiver is referenced in more than one pipeline the Collector will create only one receiver instance at runtime that will send the data to `FanOutConnector` which in turn will send the data to the first processor of each pipeline. The data propagation from receiver to `FanOutConnector` and then to processors is via synchronous function call. This means that if one processor blocks the call the other pipelines that are attached to this receiver will be blocked from receiving the same data and the receiver itself will stop processing and forwarding newly received data. + +### Exporters + +Exporters typically forward the data they get to a destination on a network (but they can also send it elsewhere, e.g “logging” exporter writes the telemetry data to a local file). + +The configuration allows to have multiple exporters of the same type, even in the same pipeline. For example one can have 2 “opencensus” exporters defined each one sending to a different opencensus endpoint, e.g.: + +```yaml +exporters: + opencensus/1: + endpoint: "example.com:14250" + opencensus/2: + endpoint: "0.0.0.0:14250" +``` + +Usually an exporter gets the data from one pipeline, however it is possible to configure multiple pipelines to send data to the same exporter, e.g.: + +```yaml +exporters: + jaeger: + protocols: + grpc: + endpoint: "0.0.0.0:14250" + +service: + pipelines: + traces: # a pipeline of “traces” type + receivers: [zipkin] + processors: [tags, tail_sampling, batch, queued_retry] + exporters: [jaeger] + traces/2: # another pipeline of “traces” type + receivers: [opencensus] + processors: [batch] + exporters: [jaeger] +``` + +In the above example “jaeger” exporter will get data from pipeline “traces” and from pipeline “traces/2”. When the Collector loads this config the result will look like this (part of processors and receivers are omitted from the diagram for brevity): + +![Exporters](images/design-exporters.png) + +### Processors + +A pipeline can contain sequentially connected processors. The first processor gets the data from one or more receivers that are configured for the pipeline, the last processor sends the data to one or more exporters that are configured for the pipeline. All processors between the first and last receive the data strictly only from one preceding processor and send data strictly only to the succeeding processor. + +Processors can transform the data before forwarding it (i.e. add or remove attributes from spans), they can drop the data simply by deciding not to forward it (this is for example how “sampling” processor works), they can also generate new data (this is how for example how a “persistent-queue” processor can work after Collector restarts by reading previously saved data from a local file and forwarding it on the pipeline). + +The same name of the processor can be referenced in the “processors” key of multiple pipelines. In this case the same configuration will be used for each of these processors however each pipeline will always gets its own instance of the processor. Each of these processors will have its own state, the processors are never shared between pipelines. For example if “queued_retry” processor is used several pipelines each pipeline will have its own queue (although the queues will be configured exactly the same way if the reference the same key in the config file). As an example, given the following config: + +```yaml +processors: + queued_retry: + size: 50 + per-exporter: true + enabled: true + +service: + pipelines: + traces: # a pipeline of “traces” type + receivers: [zipkin] + processors: [queued_retry] + exporters: [jaeger] + traces/2: # another pipeline of “traces” type + receivers: [opencensus] + processors: [queued_retry] + exporters: [opencensus] +``` + +When the Collector loads this config the result will look like this: + +![Processors](images/design-processors.png) + +Note that each “queued_retry” processor is an independent instance, although both are configured the same way, i.e. each have a size of 50. + +## Running as an Agent + +On a typical VM/container, there are user applications running in some +processes/pods with OpenTelemetry Library (Library). Previously, Library did +all the recording, collecting, sampling and aggregation on spans/stats/metrics, +and exported them to other persistent storage backends via the Library +exporters, or displayed them on local zpages. This pattern has several +drawbacks, for example: + +1. For each OpenTelemetry Library, exporters/zpages need to be re-implemented + in native languages. +2. In some programming languages (e.g Ruby, PHP), it is difficult to do the + stats aggregation in process. +3. To enable exporting OpenTelemetry spans/stats/metrics, application users + need to manually add library exporters and redeploy their binaries. This is + especially difficult when there’s already an incident and users want to use + OpenTelemetry to investigate what’s going on right away. +4. Application users need to take the responsibility in configuring and + initializing exporters. This is error-prone (e.g they may not set up the + correct credentials\monitored resources), and users may be reluctant to + “pollute” their code with OpenTelemetry. + +To resolve the issues above, you can run OpenTelemetry Collector as an Agent. +The Agent runs as a daemon in the VM/container and can be deployed independent +of Library. Once Agent is deployed and running, it should be able to retrieve +spans/stats/metrics from Library, export them to other backends. We MAY also +give Agent the ability to push configurations (e.g sampling probability) to +Library. For those languages that cannot do stats aggregation in process, they +should also be able to send raw measurements and have Agent do the aggregation. + +TODO: update the diagram below. + +![agent-architecture](https://user-images.githubusercontent.com/10536136/48792454-2a69b900-eca9-11e8-96eb-c65b2b1e4e83.png) + +For developers/maintainers of other libraries: Agent can also +accept spans/stats/metrics from other tracing/monitoring libraries, such as +Zipkin, Prometheus, etc. This is done by adding specific receivers. See +[Receivers](#receivers) for details. + +## Running as a Standalone Collector + +The OpenTelemetry Collector can run as a Standalone instance and receives spans +and metrics exported by one or more Agents or Libraries, or by +tasks/agents that emit in one of the supported protocols. The Collector is +configured to send data to the configured exporter(s). The following figure +summarizes the deployment architecture: + +TODO: update the diagram below. + +![OpenTelemetry Collector Architecture](https://user-images.githubusercontent.com/10536136/46637070-65f05f80-cb0f-11e8-96e6-bc56468486b3.png "OpenTelemetry Collector Architecture") + +The OpenTelemetry Collector can also be deployed in other configurations, such +as receiving data from other agents or clients in one of the formats supported +by its receivers. + + +### OpenCensus Protocol + +TODO: move this section somewhere else since this document is intended to describe non-protocol specific functionality. + +OpenCensus Protocol uses a bi-directional gRPC +stream. Sender should initiate the connection, since there’s only one +dedicated port for Agent, while there could be multiple instrumented processes. By default, the Collector is available on port 55678. + +#### Protocol Workflow + +1. Sender will try to directly establish connections for Config and Export + streams. +2. As the first message in each stream, Sender must send its identifier. Each + identifier should uniquely identify Sender within the VM/container. If + there is no identifier in the first message, Collector should drop the whole + message and return an error to the client. In addition, the first message + MAY contain additional data (such as `Span`s). As long as it has a valid + identifier associated, Collector should handle the data properly, as if they + were sent in a subsequent message. Identifier is no longer needed once the + streams are established. +3. On Sender side, if connection to Collector failed, Sender should retry + indefinitely if possible, subject to available/configured memory buffer size. + (Reason: consider environments where the running applications are already + instrumented with OpenTelemetry Library but Collector is not deployed yet. + Sometime in the future, we can simply roll out the Collector to those + environments and Library would automatically connect to Collector with + indefinite retries. Zero changes are required to the applications.) + Depending on the language and implementation, retry can be done in either + background or a daemon thread. Retry should be performed at a fixed + frequency (rather than exponential backoff) to have a deterministic expected + connect time. +4. On Collector side, if an established stream were disconnected, the identifier of + the corresponding Sender would be considered expired. Sender needs to + start a new connection with a unique identifier (MAY be different than the + previous one). diff --git a/internal/otel_collector/docs/images/design-exporters.png b/internal/otel_collector/docs/images/design-exporters.png new file mode 100644 index 0000000000000000000000000000000000000000..0904403388e105253acc7c60c0b5222d40ebea7b GIT binary patch literal 16807 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 literal 0 HcmV?d00001 diff --git a/internal/otel_collector/docs/images/design-pipelines.png b/internal/otel_collector/docs/images/design-pipelines.png new file mode 100644 index 0000000000000000000000000000000000000000..1b58d7fc0d9c00262ad9360fc230d611196f7d9d GIT binary patch literal 19661 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 BcP9V< literal 0 HcmV?d00001 diff --git a/internal/otel_collector/docs/images/design-receivers.png b/internal/otel_collector/docs/images/design-receivers.png new file mode 100644 index 0000000000000000000000000000000000000000..dc22432c82a3aed3099e569d494959810d6d3178 GIT binary patch literal 17407 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 literal 0 HcmV?d00001 diff --git a/internal/otel_collector/docs/images/design-service-lifecycle.png b/internal/otel_collector/docs/images/design-service-lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..ad55af383f0ff14fc71f1d87f1a07a002c684a0e GIT binary patch literal 21842 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 E0M}11Gynhq literal 0 HcmV?d00001 diff --git a/internal/otel_collector/docs/images/opentelemetry-service-deployment-models.png b/internal/otel_collector/docs/images/opentelemetry-service-deployment-models.png new file mode 100644 index 0000000000000000000000000000000000000000..b977c0de0158d69345d8255672d6b9e382e5b2b9 GIT binary patch literal 78430 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 literal 0 HcmV?d00001 diff --git a/internal/otel_collector/docs/images/zpages-example.png b/internal/otel_collector/docs/images/zpages-example.png new file mode 100644 index 0000000000000000000000000000000000000000..168004dd6fcef7cdf2cae1483b98340ec502dd6f GIT binary patch literal 373980 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

=|#7mWyVaI#RDTa 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%0A(Wv%#*sl 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 %;Lq@kGpojO{R-*xnf(Z|n!qSafqfj)t}PA@E< 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 z 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 0` and +`otelcol_processor_dropped_metric_points > 0` to detect data loss, depending on +the requirements set up a minimal time window before alerting, avoiding +notifications for small losses that are not considered outages or within the +desired reliability level. + +### Low on CPU Resources + +This depends on the CPU metrics available on the deployment, eg.: +`kube_pod_container_resource_limits_cpu_cores` for Kubernetes. Let's call it +`available_cores` below. The idea here is to have an upper bound of the number +of available cores, and the maximum expected ingestion rate considered safe, +let's call it `safe_rate`, per core. This should trigger increase of resources/ +instances (or raise an alert as appropriate) whenever +`(actual_rate/available_cores) < safe_rate`. + +The `safe_rate` depends on the specific configuration being used. +// TODO: Provide reference `safe_rate` for a few selected configurations. + +## Secondary Monitoring + +### Queue Length + +The `queued_retry` processor is recommended as the retry mechanism for the +Collector and as such should be used in any production deployment. +The `queued_retry` processor provides the +`otelcol_processor_queued_retry_queue_length` metric, besides others. +When this metric is growing constantly it is an indication that the Collector +is not able to send data as fast as it is receiving. +This will precede data loss and also can indicate a Collector low on resources. + +### Receive Failures + +Sustained rates of `otelcol_receiver_refused_spans` and +`otelcol_receiver_refused_metric_points` indicate too many errors returned to +clients. Depending on the deployment and the client’s resilience this may +indicate data loss at the clients. + +Sustained rates of `otelcol_exporter_send_failed_spans` and +`otelcol_exporter_send_failed_metric_points` indicate that the Collector is not +able to export data as expected. +It doesn't imply data loss per se since there could be retries but a high rate +of failures could indicate issues with the network or backend receiving the +data. + +## Data Flow + +### Data Ingress + +The `otelcol_receiver_accepted_spans` and +`otelcol_receiver_accepted_metric_points` metrics provide information about +the data ingested by the Collector. + +### Data Egress + +The `otecol_exporter_sent_spans` and +`otelcol_exporter_sent_metric_points`metrics provide information about +the data exported by the Collector. diff --git a/internal/otel_collector/docs/observability.md b/internal/otel_collector/docs/observability.md new file mode 100644 index 00000000000..bf33f082e5f --- /dev/null +++ b/internal/otel_collector/docs/observability.md @@ -0,0 +1,91 @@ +# OpenTelemetry Collector Observability + +## Goal + +The goal of this document is to have a comprehensive description of observability of the Collector and changes needed to achieve observability part of our [vision](vision.md). + +## What Needs Observation + +The following elements of the Collector need to be observable. + +### Current Values + +- Resource consumption: CPU, RAM (in the future also IO - if we implement persistent queues) and any other metrics that may be available to Go apps (e.g. garbage size, etc). + +- Receiving data rate, broken down by receivers and by data type (traces/metrics). + +- Exporting data rate, broken down by exporters and by data type (traces/metrics). + +- Data drop rate due to throttling, broken down by data type. + +- Data drop rate due to invalid data received, broken down by data type. + +- Current throttling state: Not Throttled/Throttled by Downstream/Internally Saturated. + +- Incoming connection count, broken down by receiver. + +- Incoming connection rate (new connections per second), broken down by receiver. + +- In-memory queue size (in bytes and in units). Note: measurements in bytes may be difficult / expensive to obtain and should be used cautiously. + +- Persistent queue size (when supported). + +- End-to-end latency (from receiver input to exporter output). Note that with multiple receivers/exporters we potentially have NxM data paths, each with different latency (plus different pipelines in the future), so realistically we should likely expose the average of all data paths (perhaps broken down by pipeline). + +- Latency broken down by pipeline elements (including exporter network roundtrip latency for request/response protocols). + +“Rate” values must reflect the average rate of the last 10 seconds. Rates must exposed in bytes/sec and units/sec (e.g. spans/sec). + +Note: some of the current values and rates may be calculated as derivatives of cumulative values in the backend, so it is an open question if we want to expose them separately or no. + +### Cumulative Values + +- Total received data, broken down by receivers and by data type (traces/metrics). + +- Total exported data, broken down by exporters and by data type (traces/metrics). + +- Total dropped data due to throttling, broken down by data type. + +- Total dropped data due to invalid data received, broken down by data type. + +- Total incoming connection count, broken down by receiver. + +- Uptime since start. + +### Trace or Log on Events + +We want to generate the following events (log and/or send as a trace with additional data): + +- Collector started/stopped. + +- Collector reconfigured (if we support on-the-fly reconfiguration). + +- Begin dropping due to throttling (include throttling reason, e.g. local saturation, downstream saturation, downstream unavailable, etc). + +- Stop dropping due to throttling. + +- Begin dropping due to invalid data (include sample/first invalid data). + +- Stop dropping due to invalid data. + +- Crash detected (differentiate clean stopping and crash, possibly include crash data if available). + +For begin/stop events we need to define an appropriate hysteresis to avoid generating too many events. Note that begin/stop events cannot be detected in the backend simply as derivatives of current rates, the events include additional data that is not present in the current value. + +### Host Metrics + +The service should collect host resource metrics in addition to service's own process metrics. This may help to understand that the problem that we observe in the service is induced by a different process on the same host. + +## How We Expose Metrics/Traces + +Collector configuration must allow specifying the target for own metrics/traces (which can be different from the target of collected data). The metrics and traces must be clearly tagged to indicate that they are service’s own metrics (to avoid conflating with collected data in the backend). + +### Impact + +We need to be able to assess the impact of these observability improvements on the core performance of the Collector. + +### Configurable Level of Observability + +Some of the metrics/traces can be high volume and may not be desirable to always observe. We should consider adding an observability verboseness “level” that allows configuring the Collector to send more or less observability data (or even finer granularity to allow turning on/off specific metrics). + +The default level of observability must be defined in a way that has insignificant performance impact on the service. diff --git a/internal/otel_collector/docs/performance.md b/internal/otel_collector/docs/performance.md new file mode 100644 index 00000000000..98901196cd0 --- /dev/null +++ b/internal/otel_collector/docs/performance.md @@ -0,0 +1,72 @@ +# OpenTelemetry Collector Performance + +The performance numbers that follow were generated using version 0.1.3 of the +OpenTelemetry Collector, are applicable primarily to the OpenTelemetry Collector and +are measured only for traces. In the future, more configurations will be tested. + +Note with the OpenTelemetry Agent you can expect as good if not better performance +with lower resource utilization. This is because the OpenTelemetry Agent does not +today support features such as batching or retries and will not support +tail_sampling. + +It is important to note that the performance of the OpenTelemetry Collector depends +on a variety of factors including: + +* The receiving format: OpenTelemetry (55678), Jaeger thrift (14268) or Zipkin v2 JSON (9411) +* The size of the spans (tests are based on number of attributes): 20 +* Whether tail_sampling is enabled or not +* CPU / Memory allocation +* Operating System: Linux + +## Testing + +Testing was completed on Linux using the [Synthetic Load Generator +utility](https://github.com/Omnition/synthetic-load-generator) running for a +minimum of one hour (i.e. sustained rate). You can be reproduce these results in +your own environment using the parameters described in this document. It is +important to note that this utility has a few configurable parameters which can +impact the results of the tests. The parameters used are defined below. + +* FlushInterval(ms) [default: 1000] +* MaxQueueSize [default: 100] +* SubmissionRate(spans/sec): 100,000 + +## Results without tail-based sampling + +| Span
Format | CPU
(2+ GHz) | RAM
(GB) | Sustained
Rate | Recommended
Maximum | +| :---: | :---: | :---: | :---: | :---: | +| OpenTelemetry | 1 | 2 | ~12K | 10K | +| OpenTelemetry | 2 | 4 | ~24K | 20K | +| Jaeger Thrift | 1 | 2 | ~14K | 12K | +| Jaeger Thrift | 2 | 4 | ~27.5K | 24K | +| Zipkin v2 JSON | 1 | 2 | ~10.5K | 9K | +| Zipkin v2 JSON | 2 | 4 | ~22K | 18K | + +If you are NOT using tail-based sampling and you need higher rates then you can +either: + +* Divide traffic to different collector (e.g. by region) +* Scale-up by adding more resources (CPU/RAM) +* Scale-out by putting one or more collectors behind a load balancer or k8s +service + +## Results with tail-based sampling + +> Note: Additional memory is required for tail-based sampling + +| Span
Format | CPU
(2+ GHz) | RAM
(GB) | Sustained
Rate | Recommended
Maximum | +| :---: | :---: | :---: | :---: | :---: | +| OpenTelemetry | 1 | 2 | ~9K | 8K | +| OpenTelemetry | 2 | 4 | ~18K | 16K | +| Jaeger Thrift | 1 | 6 | ~11.5K | 10K | +| Jaeger Thrift | 2 | 8 | ~23K | 20K | +| Zipkin v2 JSON | 1 | 6 | ~8.5K | 7K | +| Zipkin v2 JSON | 2 | 8 | ~16K | 14K | + +If you are using tail-based sampling and you need higher rates then you can +either: + +* Scale-up by adding more resources (CPU/RAM) +* Scale-out by putting one or more collectors behind a load balancer or k8s +service, but the load balancer must support traceID-based routing (i.e. all +spans for a given traceID need to be received by the same collector instance) diff --git a/internal/otel_collector/docs/release.md b/internal/otel_collector/docs/release.md new file mode 100644 index 00000000000..092c031b722 --- /dev/null +++ b/internal/otel_collector/docs/release.md @@ -0,0 +1,39 @@ +# OpenTelemetry Collector Release Procedure + +Collector build and testing is currently fully automated. However there are still certain operations that need to be performed manually in order to make a release. + +We release both core and contrib collectors with the same versions where the contrib release uses the core release as a dependency. We’ve divided this process into two sections. A release engineer must first release the Core collector and then the Contrib collector. + +Important: Note that you’ll need to be able to sign git commits/tags in order to be able to release a collector version. Follow [this guide](https://docs.github.com/en/github/authenticating-to-github/signing-commits) to setup it up. + +Note: You’ll need to be an approver for both the repos in order to be able to make the release. This is required as you’ll need to push tags and commits directly to the upstream repo. + +## Releasing OpenTelemetry Core + +1. Update Contrib to use the latest in development version of Core. Run `make update-otel` in Contrib root directory and if it results in any changes, submit a PR. Get the PR approved and merged. This is to ensure that the latest core does not break contrib in any way. We’ll update it once more to the final release number later. Make sure contrib builds and end-to-end tests pass successfully after being merged and -dev docker images are published. + +1. Determine the version number that will be assigned to the release. Collector uses semver, with the exception that while we are still in Beta stage breaking changes are allowed without incrementing major version number. For breaking changes we increment the minor version number and set the patch number to 0. + +1. Prepare Core for release. Update CHANGELOG.md file and rename the Unreleased section to the new release name. Add a new unreleased section at top. + + Use commit history feature to get the list of commits since the last release to help understand what should be in the release notes, e.g.: https://github.com/open-telemetry/opentelemetry-collector-contrib/compare/${last_release}...master. Submit a PR with the changes and get the PR approved and merged. + +1. Make sure the current master branch build successfully passes (Core and Contrib). For Contrib also check that the spawn-stability-tests-job triggered by the main build-publish job also passes. Check that the corresponding "-dev" images exist in Dockerhub (Core and Contrib). + +1. Create a branch named release/ (e.g. release/v0.4.x) in Core from the changelog update commit and push to origin (not your fork). Wait for the release branch builds to pass successfully. + +1. Tag all the modules with the new release version by running the `make add-tag` command (e.g. `make add-tag TAG=v0.4.0`). Push them to origin (not your fork) with `git push --tags origin` (assuming origin refers to upstream open-telemetry project). Wait for the new tag build to pass successfully. This build will push new docker images to https://hub.docker.com/repository/docker/otel/opentelemetry-collector, create a Github release for the tag and push all the build artifacts to the Github release. + +1. Edit the newly auto-created Github release and copy release notes from the CHANGELOG.md file to the release. This step should be automated. CI can pull the release notes from the change log and use it as the body when creating the new release. + +## Releasing OpenTelemetry Contrib + +1. Prepare Contrib for release. Update CHANGELOG.md file and rename the Unreleased section to the new release name. Add a new unreleased section at top. Refer to Core release notes (assuming the previous release of Core and Contrib was also performed simultaneously), and in addition to that list changes that happened in the Contrib repo. + +1. Update the Core dependency to the Core version we just released with `make update-otel` command, e.g, `make update-otel OTEL_VERSION=v0.4.0`. Create a PR with both the changes, get it approved and merged. + +1. Create a branch named release/ (e.g. release/v0.4.x) in Core from the changelog update commit and push to origin (not your fork). Wait for the release branch builds to pass successfully. + +1. Tag all the modules with the new release version by running the `make add-tag` command (e.g. `make add-tag TAG=v0.4.0`). Push them to origin (not your fork) with `git push --tags origin` (assuming origin refers to upstream open-telemetry project). Wait for the new tag build to pass successfully. This build will push new docker images to https://hub.docker.com/repository/docker/otel/opentelemetry-collector-contrib, create a Github release for the tag and push all the build artifacts to the Github release. + +1. Edit the newly auto-created Github release and copy release notes from the CHANGELOG.md file to the release. This step should be automated. CI can pull the release notes from the change log and use it as the body when creating the new release. diff --git a/internal/otel_collector/docs/roadmap.md b/internal/otel_collector/docs/roadmap.md new file mode 100644 index 00000000000..7583ca36943 --- /dev/null +++ b/internal/otel_collector/docs/roadmap.md @@ -0,0 +1,27 @@ +# Long-term Roadmap + +This long-term roadmap (draft) is a vision document that reflects our +current desires. It is not a commitment to implement everything listed in this roadmap. +The primary purpose of this document is to ensure that all contributors work in alignment. +As our vision changes over time, maintainers reserve the right to add, modify, and _remove_ +items from this roadmap. + +Description|Status|Links| +-----------|------|-----| +**Testing**| +Metrics correctness tests|In progress|[#652](https://github.com/open-telemetry/opentelemetry-collector/issues/652) +| | +**New Formats**| +Complete OTLP/HTTP support| |[#882](https://github.com/open-telemetry/opentelemetry-collector/issues/882) +Add logs support for all primary core processors (attributes, batch, k8s_tagger, etc)|In progress| +| | +**5 Min to Value**| +Distribution packages for most common targets (e.g. Docker, RPM, Windows, etc)| +Detection and collection of environment metrics and tags on AWS|| +Detection and collection of k8s telemetry|In progress| +Host metric collection|In progress| +Support more application-specific metric collection (e.g. Kafka, Hadoop, etc) +| | +**Other Features**| +Graceful shutdown (pipeline draining)| |[#483](https://github.com/open-telemetry/opentelemetry-collector/issues/483) +Deprecate queue retry processor and enable queuing per exporter by default||[#1721](https://github.com/open-telemetry/opentelemetry-collector/issues/1721) diff --git a/internal/otel_collector/docs/service-extensions.md b/internal/otel_collector/docs/service-extensions.md new file mode 100644 index 00000000000..1ae5b9c4ca9 --- /dev/null +++ b/internal/otel_collector/docs/service-extensions.md @@ -0,0 +1,146 @@ +# OpenTelemetry Collector: Extensions + +Besides the pipeline elements (receivers, processors, and exporters) the Collector +uses various service extensions (e.g.: healthcheck, z-pages, etc). +This document describes the “extensions” design and how they are implemented. + +## Configuration and Interface + +The configuration follows the same pattern used for pipelines: a base +configuration type and the creation of factories to instantiate the extension +objects. + +In order to support generic service extensions an interface is defined +so the service can interact uniformly with these. At minimum service extensions +need to implement the interface that covers Start and Shutdown. + +In addition to this base interface there is support to notify extensions when +pipelines are “ready” and when they are about to be stopped, i.e.: “not ready” +to receive data. These are a necessary addition to allow implementing extensions +that indicate to LBs and external systems if the service instance is ready or +not to receive data +(e.g.: a [k8s readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#define-readiness-probes)). +These state changes are under the control of the service application hosting +the extensions. + +There are more complex scenarios in which there can be notifications of state +changes from the extensions to their host. These more complex cases are not +supported at this moment, but this design doesn’t prevent such extensions in the +future[^1]. + + +## Collector State and Extensions + +The diagram below shows the basic state transitions of the OpenTelemetry Collector +and how it will interact with the service extensions. + +![ServiceLifeCycle](images/design-service-lifecycle.png) + + +## Configuration + +The config package will be extended to load the service extensions when the +configuration is loaded. The settings for service extensions will live in the +same configuration file as the pipeline elements. Below is an example of how +these sections would look like in the configuration file: + +```yaml + +# Example of the extensions available with the core Collector. The list below +# includes all configurable options and their respective default value. +extensions: + health_check: + port: 13133 + pprof: + endpoint: "localhost:1777" + block_profile_fraction: 0 + mutex_profile_fraction: 0 + zpages: + endpoint: "localhost:55679" + +# The service lists extensions not directly related to data pipelines, but used +# by the service. +service: + # extensions lists the extensions added to the service. They are started + # in the order presented below and stopped in the reverse order. + extensions: [health_check, pprof, zpages] +``` + +The configuration base type does not share any common fields. + +The configuration, analogous to pipelines, allows to have multiple extensions of +the same type. Implementers of extensions need to take care to return error +if it can only execute a single instance. (Note: the configuration uses composite +key names in the form of `type[/name]` +as defined in this [this document](https://docs.google.com/document/d/1NeheFG7DmcUYo_h2vLtNRlia9x5wOJMlV4QKEK05FhQ/edit#)). + +The factory follows the same pattern established for pipeline configuration: + +```go +// Factory is a factory interface for extensions to the service. +type Factory interface { + // Type gets the type of the extension created by this factory. + Type() string + + // CreateDefaultConfig creates the default configuration for the extension. + CreateDefaultConfig() configmodels.Extension + + // CreateExtension creates a service extension based on the given config. + CreateExtension(logger *zap.Logger, cfg configmodels.Extension) (ServiceExtension, error) +} +``` + + +## Extension Interface + +The interface defined below is the minimum required for +extensions in use on the service: + +```go +// ServiceExtension is the interface for objects hosted by the OpenTelemetry Collector that +// don't participate directly on data pipelines but provide some functionality +// to the service, examples: health check endpoint, z-pages, etc. +type ServiceExtension interface { + // Start the ServiceExtension object hosted by the given host. At this point in the + // process life-cycle the receivers are not started and the host did not + // receive any data yet. + Start(host Host) error + + // Shutdown the ServiceExtension instance. This happens after the pipelines were + // shutdown. + Shutdown() error +} + +// PipelineWatcher is an extra interface for ServiceExtension hosted by the OpenTelemetry +// Collector that is to be implemented by extensions interested in changes to pipeline +// states. Typically this will be used by extensions that change their behavior if data is +// being ingested or not, e.g.: a k8s readiness probe. +type PipelineWatcher interface { + // Ready notifies the ServiceExtension that all pipelines were built and the + // receivers were started, i.e.: the service is ready to receive data + // (notice that it may already have received data when this method is called). + Ready() error + + // NotReady notifies the ServiceExtension that all receivers are about to be stopped, + // i.e.: pipeline receivers will not accept new data. + // This is sent before receivers are stopped, so the ServiceExtension can take any + // appropriate action before that happens. + NotReady() error +} + +// Host represents the entity where the extension is being hosted. +// It is used to allow communication between the extension and its host. +type Host interface { + // ReportFatalError is used to report to the host that the extension + // encountered a fatal error (i.e.: an error that the instance can't recover + // from) after its start function had already returned. + ReportFatalError(err error) +} +``` + +## Notes + +[^1]: + This can be done by adding specific interfaces to extension types that support + those and having the service checking which of the extension instances support + each interface. diff --git a/internal/otel_collector/docs/troubleshooting.md b/internal/otel_collector/docs/troubleshooting.md new file mode 100644 index 00000000000..465695a8682 --- /dev/null +++ b/internal/otel_collector/docs/troubleshooting.md @@ -0,0 +1,260 @@ +# Troubleshooting + +## Observability + +The Collector offers multiple ways to measure the health of the Collector +as well as investigate issues. + +### Logs + +Logs can be helpful in identifying issues. Always start by checking the log +output and looking for potential issues. + +The verbosity level, which defaults to `INFO` can also be adjusted by passing +the `--log-level` flag to the `otelcol` process. See `--help` for more details. + +```bash +$ otelcol --log-level DEBUG +``` + +### Metrics + +Prometheus metrics are exposed locally on port `8888` and path `/metrics`. + +For containerized environments it may be desirable to expose this port on a +public interface instead of just locally. The metrics address can be configured +by passing the `--metrics-addr` flag to the `otelcol` process. See `--help` for +more details. + +```bash +$ otelcol --metrics-addr 0.0.0.0:8888 +``` + +A grafana dashboard for these metrics can be found +[here](https://grafana.com/grafana/dashboards/11575). + +Also note that a Collector can be configured to scrape its own metrics and send +it through configured pipelines. For example: + +```yaml +receivers: + prometheus: + config: + scrape_configs: + - job_name: 'otelcol' + scrape_interval: 10s + static_configs: + - targets: ['0.0.0.0:8888'] + metric_relabel_configs: + - source_labels: [ __name__ ] + regex: '.*grpc_io.*' + action: drop +exporters: + logging: +service: + pipelines: + metrics: + receivers: [prometheus] + processors: [] + exporters: [logging] +``` + +### zPages + +The +[zpages](https://github.com/open-telemetry/opentelemetry-collector/tree/master/extension/zpagesextension/README.md) +extension, which if enabled is exposed locally on port `55679`, can be used to +check receivers and exporters trace operations via `/debug/tracez`. `zpages` +may contain error logs that the Collector does not emit. + +For containerized environments it may be desirable to expose this port on a +public interface instead of just locally. This can be configured via the +extensions configuration section. For example: + +```yaml +extensions: + zpages: + endpoint: 0.0.0.0:55679 +``` + +### Local exporters + +[Local +exporters](https://github.com/open-telemetry/opentelemetry-collector/tree/master/exporter#general-information) +can be configured to inspect the data being processed by the Collector. + +For live troubleshooting purposes consider leveraging the `logging` exporter, +which can be used to confirm that data is being received, processed and +exported by the Collector. + +```yaml +receivers: + zipkin: +exporters: + logging: +service: + pipelines: + traces: + receivers: [zipkin] + processors: [] + exporters: [logging] +``` + +Get a Zipkin payload to test. For example create a file called `trace.json` +that contains: + +```json +[ + { + "traceId": "5982fe77008310cc80f1da5e10147519", + "parentId": "90394f6bcffb5d13", + "id": "67fae42571535f60", + "kind": "SERVER", + "name": "/m/n/2.6.1", + "timestamp": 1516781775726000, + "duration": 26000, + "localEndpoint": { + "serviceName": "api" + }, + "remoteEndpoint": { + "serviceName": "apip" + }, + "tags": { + "data.http_response_code": "201", + } + } +] +``` + +With the Collector running, send this payload to the Collector. For example: + +```bash +$ curl -X POST localhost:9411/api/v2/spans -H'Content-Type: application/json' -d @trace.json +``` + +You should see a log entry like the following from the Collector: + +```json +2020-11-11T04:12:33.089Z INFO loggingexporter/logging_exporter.go:296 TraceExporter {"#spans": 1} +``` + +You can also configure the `logging` exporter so the entire payload is printed: + +```yaml +exporters: + logging: + loglevel: debug +``` + +With the modified configuration if you re-run the test above the log output should look like: + +```json +2020-11-11T04:08:17.344Z DEBUG loggingexporter/logging_exporter.go:353 ResourceSpans #0 +Resource labels: + -> service.name: STRING(api) +InstrumentationLibrarySpans #0 +Span #0 + Trace ID : 5982fe77008310cc80f1da5e10147519 + Parent ID : 90394f6bcffb5d13 + ID : 67fae42571535f60 + Name : /m/n/2.6.1 + Kind : SPAN_KIND_SERVER + Start time : 2018-01-24 08:16:15.726 +0000 UTC + End time : 2018-01-24 08:16:15.752 +0000 UTC +Attributes: + -> data.http_response_code: STRING(201) +``` + +### Health Check + +The +[health_check](https://github.com/open-telemetry/opentelemetry-collector/tree/master/extension/healthcheckextension/README.md) +extension, which by default is available on all interfaces on port `13133`, can +be used to ensure the Collector is functioning properly. + +```yaml +extensions: + health_check: +service: + extensions: [health_check] +``` + +It returns a response like the following: + +```json +{"status":"Server available","upSince":"2020-11-11T04:12:31.6847174Z","uptime":"49.0132518s"} +``` + +### pprof + +The +[pprof](https://github.com/open-telemetry/opentelemetry-collector/tree/master/extension/pprofextension/README.md) +extension, which by default is available locally on port `1777`, allows you to profile the +Collector as it runs. This is an advanced use-case that should not be needed in most circumstances. + +## Common Issues + +### Collector exit/restart + +The Collector may exit/restart because: + +- Memory pressure due to missing or misconfigured + [memory_limiter](https://github.com/open-telemetry/opentelemetry-collector/blob/master/processor/memorylimiter/README.md) + processor. +- [Improperly sized](https://github.com/open-telemetry/opentelemetry-collector/blob/master/docs/performance.md) + for load. +- Improperly configured (for example, a queue size configured higher + than available memory). +- Infrastructure resource limits (for example Kubernetes). + +### Data being dropped + +Data may be dropped for a variety of reasons, but most commonly because of an: + +- [Improperly sized Collector](https://github.com/open-telemetry/opentelemetry-collector/blob/master/docs/performance.md) resulting in Collector being unable to process and export the data as fast as it is received. +- Exporter destination unavailable or accepting the data too slowly. + +To mitigate drops, it is highly recommended to configure the +[batch](https://github.com/open-telemetry/opentelemetry-collector/blob/master/processor/batchprocessor/README.md) +processor. In addition, it may be necessary to configure the [queued retry +options](https://github.com/open-telemetry/opentelemetry-collector/tree/master/exporter/exporterhelper#configuration) +on enabled exporters. + +### Receiving data not working + +If you are unable to receive data then this is likely because +either: + +- There is a network configuration issue +- The receiver configuration is incorrect +- The receiver is defined in the `receivers` section, but not enabled in any `pipelines` +- The client configuration is incorrect + +Check the Collector logs as well as `zpages` for potential issues. + +### Processing data not working + +Most processing issues are a result of either a misunderstanding of how the +processor works or a misconfiguration of the processor. + +Examples of misunderstanding include: + +- The attributes processors only work for "tags" on spans. Span name is + handled by the span processor. +- Processors for trace data (except tail sampling) work on individual spans. + +### Exporting data not working + +If you are unable to export to a destination then this is likely because +either: + +- There is a network configuration issue +- The exporter configuration is incorrect +- The destination is unavailable + +Check the collector logs as well as `zpages` for potential issues. + +More often than not, exporting data does not work because of a network +configuration issue. This could be due to a firewall, DNS, or proxy +issue. Note that the Collector does have +[proxy support](https://github.com/open-telemetry/opentelemetry-collector/tree/master/exporter#proxy-support). diff --git a/internal/otel_collector/docs/vision.md b/internal/otel_collector/docs/vision.md new file mode 100644 index 00000000000..dfe9ab01934 --- /dev/null +++ b/internal/otel_collector/docs/vision.md @@ -0,0 +1,25 @@ +# OpenTelemetry Collector Long-term Vision + +The following are high-level items that define our long-term vision for OpenTelemetry Collector, what we aspire to achieve. This vision is our daily guidance when we design new features and make changes to the Collector. + +This is a living document that is expected to evolve over time. + +## Performant +Highly stable and performant under varying loads. Well-behaved under extreme load, with predictable, low resource consumption. + +## Observable +Expose own operational metrics in a clear way. Be an exemplar of observable service. Allow configuring the level of observability (more or less metrics, traces, logs, etc reported). See [more details](observability.md). + +## Multi-Data +Support traces, metrics, logs and other relevant data types. + +## Usable Out of the Box +Reasonable default configuration, supports popular protocols, runs and collects out of the box. + +## Extensible +Extensible and customizable without touching the core code. Can create custom agents based on the core and extend with own components. Welcoming 3rd party contribution policy. + +## Unified Codebase +One codebase for daemon (Agent) and standalone service (Collector). + +For more details on how we plan to achieve this vision please see the [Roadmap](roadmap.md). \ No newline at end of file diff --git a/internal/otel_collector/examples/README.md b/internal/otel_collector/examples/README.md new file mode 100644 index 00000000000..1c5da97badb --- /dev/null +++ b/internal/otel_collector/examples/README.md @@ -0,0 +1,5 @@ +# Examples + +Information on how the examples can be used can be found in the [Getting +Started +documentation](https://opentelemetry.io/docs/collector/getting-started/). diff --git a/internal/otel_collector/examples/demo/.env b/internal/otel_collector/examples/demo/.env new file mode 100644 index 00000000000..e25dbb8143a --- /dev/null +++ b/internal/otel_collector/examples/demo/.env @@ -0,0 +1,2 @@ +OTELCOL_IMG=otel/opentelemetry-collector-dev:latest +OTELCOL_ARGS= diff --git a/internal/otel_collector/examples/demo/README.md b/internal/otel_collector/examples/demo/README.md new file mode 100644 index 00000000000..a218ba3daad --- /dev/null +++ b/internal/otel_collector/examples/demo/README.md @@ -0,0 +1,49 @@ +# OpenTelemetry Collector Demo + +*IMPORTANT:* This is a pre-released version of the OpenTelemetry Collector. + +This demo presents the typical flow of observability data with multiple +OpenTelemetry Collectors deployed: + +- Applications send data directly to a Collector configured to use fewer + resources, aka the _agent_; +- The agent then forwards the data to Collector(s) that receive data from + multiple agents. Collectors on this layer typically are allowed to use more + resources and queue more data; +- The Collector then sends the data to the appropriate backend, in this demo + Jaeger, Zipkin, and Prometheus; + +This demo uses `docker-compose` and by default runs against the +`otel/opentelemetry-collector-dev:latest` image. To run the demo, switch +to the `examples/demo` folder and run: + +```shell +docker-compose up -d +``` + +The demo exposes the following backends: + +- Jaeger at http://0.0.0.0:16686 +- Zipkin at http://0.0.0.0:9411 +- Prometheus at http://0.0.0.0:9090 + +Notes: + +- It may take some time for the application metrics to appear on the Prometheus + dashboard; + +To clean up any docker container from the demo run `docker-compose down` from +the `examples/demo` folder. + +### Using a Locally Built Image +Developers interested in running a local build of the Collector need to build a +docker image using the command below: + +```shell +make docker-otelcol +``` + +And set an environment variable `OTELCOL_IMG` to `otelcol:latest` before +launching the command `docker-compose up -d`. + + diff --git a/internal/otel_collector/examples/demo/docker-compose.yaml b/internal/otel_collector/examples/demo/docker-compose.yaml new file mode 100644 index 00000000000..612a6eefaf8 --- /dev/null +++ b/internal/otel_collector/examples/demo/docker-compose.yaml @@ -0,0 +1,83 @@ +version: "2" +services: + + # Jaeger + jaeger-all-in-one: + image: jaegertracing/all-in-one:latest + ports: + - "16686:16686" + - "14268" + - "14250" + + # Zipkin + zipkin-all-in-one: + image: openzipkin/zipkin:latest + ports: + - "9411:9411" + + # Collector + otel-collector: + image: ${OTELCOL_IMG} + command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] + volumes: + - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml + ports: + - "1888:1888" # pprof extension + - "8888:8888" # Prometheus metrics exposed by the collector + - "8889:8889" # Prometheus exporter metrics + - "13133:13133" # health_check extension + - "55678" # OpenCensus receiver + - "55670:55679" # zpages extension + depends_on: + - jaeger-all-in-one + - zipkin-all-in-one + + # Agent + otel-agent: + image: ${OTELCOL_IMG} + command: ["--config=/etc/otel-agent-config.yaml", "${OTELCOL_ARGS}"] + volumes: + - ./otel-agent-config.yaml:/etc/otel-agent-config.yaml + ports: + - "1777:1777" # pprof extension + - "8887:8888" # Prometheus metrics exposed by the agent + - "14268" # Jaeger receiver + - "55678" # OpenCensus receiver + - "55679:55679" # zpages extension + - "13133" # health_check + depends_on: + - otel-collector + + # Synthetic load generators + jaeger-emitter: + image: omnition/synthetic-load-generator:1.0.25 + environment: + - JAEGER_COLLECTOR_URL=http://otel-agent:14268 + depends_on: + - otel-agent + + zipkin-emitter: + image: omnition/synthetic-load-generator:1.0.25 + environment: + - ZIPKINV2_JSON_URL=http://otel-agent:9411/api/v2/spans + depends_on: + - otel-agent + + metrics-load-generator: + image: golang:1.12.7 + volumes: + - ./app/main.go:/usr/src/main.go + environment: + - GO111MODULE=on + - OTEL_AGENT_ENDPOINT=otel-agent:55678 + command: ["bash", "-c", "go run /usr/src/main.go"] + depends_on: + - otel-agent + + prometheus: + container_name: prometheus + image: prom/prometheus:latest + volumes: + - ./prometheus.yaml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" diff --git a/internal/otel_collector/examples/demo/otel-agent-config.yaml b/internal/otel_collector/examples/demo/otel-agent-config.yaml new file mode 100644 index 00000000000..99cde8ca540 --- /dev/null +++ b/internal/otel_collector/examples/demo/otel-agent-config.yaml @@ -0,0 +1,38 @@ +receivers: + opencensus: + zipkin: + endpoint: :9411 + jaeger: + protocols: + thrift_http: + + +exporters: + opencensus: + endpoint: "otel-collector:55678" + insecure: true + logging: + loglevel: debug + +processors: + batch: + queued_retry: + +extensions: + pprof: + endpoint: :1777 + zpages: + endpoint: :55679 + health_check: + +service: + extensions: [health_check, pprof, zpages] + pipelines: + traces: + receivers: [opencensus, jaeger, zipkin] + processors: [batch, queued_retry] + exporters: [opencensus, logging] + metrics: + receivers: [opencensus] + processors: [batch] + exporters: [logging,opencensus] diff --git a/internal/otel_collector/examples/demo/otel-collector-config.yaml b/internal/otel_collector/examples/demo/otel-collector-config.yaml new file mode 100644 index 00000000000..4eff96489ea --- /dev/null +++ b/internal/otel_collector/examples/demo/otel-collector-config.yaml @@ -0,0 +1,47 @@ +receivers: + opencensus: + +exporters: + prometheus: + endpoint: "0.0.0.0:8889" + namespace: promexample + const_labels: + label1: value1 + logging: + + zipkin: + endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" + format: proto + + jaeger: + endpoint: jaeger-all-in-one:14250 + insecure: true + +# Alternatively, use jaeger_thrift_http with the settings below. In this case +# update the list of exporters on the traces pipeline. +# +# jaeger_thrift_http: +# url: http://jaeger-all-in-one:14268/api/traces + +processors: + batch: + queued_retry: + +extensions: + health_check: + pprof: + endpoint: :1888 + zpages: + endpoint: :55679 + +service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [opencensus] + processors: [batch, queued_retry] + exporters: [logging, zipkin, jaeger] + metrics: + receivers: [opencensus] + processors: [batch] + exporters: [logging,prometheus] diff --git a/internal/otel_collector/examples/demo/prometheus.yaml b/internal/otel_collector/examples/demo/prometheus.yaml new file mode 100644 index 00000000000..a8477547ecf --- /dev/null +++ b/internal/otel_collector/examples/demo/prometheus.yaml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: ['otel-collector:8889'] + - targets: ['otel-collector:8888'] diff --git a/internal/otel_collector/examples/k8s/otel-config.yaml b/internal/otel_collector/examples/k8s/otel-config.yaml new file mode 100644 index 00000000000..c5ab4fd799d --- /dev/null +++ b/internal/otel_collector/examples/k8s/otel-config.yaml @@ -0,0 +1,245 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: otel-agent-conf + labels: + app: opentelemetry + component: otel-agent-conf +data: + otel-agent-config: | + receivers: + otlp: + protocols: + grpc: + http: + exporters: + otlp: + endpoint: "otel-collector.default:55680" # TODO: Update me + insecure: true + processors: + batch: + memory_limiter: + # Same as --mem-ballast-size-mib CLI argument + ballast_size_mib: 165 + # 80% of maximum memory up to 2G + limit_mib: 400 + # 25% of limit up to 2G + spike_limit_mib: 100 + check_interval: 5s + queued_retry: + num_workers: 4 + queue_size: 100 + retry_on_failure: true + extensions: + health_check: {} + zpages: {} + service: + extensions: [health_check, zpages] + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, batch, queued_retry] + exporters: [otlp] +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: otel-agent + labels: + app: opentelemetry + component: otel-agent +spec: + selector: + matchLabels: + app: opentelemetry + component: otel-agent + template: + metadata: + labels: + app: opentelemetry + component: otel-agent + spec: + containers: + - command: + - "/otelcol" + - "--config=/conf/otel-agent-config.yaml" + # Memory Ballast size should be max 1/3 to 1/2 of memory. + - "--mem-ballast-size-mib=165" + image: otel/opentelemetry-collector-dev:latest + name: otel-agent + resources: + limits: + cpu: 500m + memory: 500Mi + requests: + cpu: 100m + memory: 100Mi + ports: + - containerPort: 55679 # ZPages endpoint. + - containerPort: 55680 # Default OpenTelemetry receiver port. + - containerPort: 8888 # Metrics. + volumeMounts: + - name: otel-agent-config-vol + mountPath: /conf + livenessProbe: + httpGet: + path: / + port: 13133 # Health Check extension default port. + readinessProbe: + httpGet: + path: / + port: 13133 # Health Check extension default port. + volumes: + - configMap: + name: otel-agent-conf + items: + - key: otel-agent-config + path: otel-agent-config.yaml + name: otel-agent-config-vol +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: otel-collector-conf + labels: + app: opentelemetry + component: otel-collector-conf +data: + otel-collector-config: | + receivers: + otlp: + protocols: + grpc: + http: + jaeger: + protocols: + grpc: + thrift_http: + zipkin: {} + processors: + batch: + memory_limiter: + # Same as --mem-ballast-size-mib CLI argument + ballast_size_mib: 683 + # 80% of maximum memory up to 2G + limit_mib: 1500 + # 25% of limit up to 2G + spike_limit_mib: 512 + check_interval: 5s + queued_retry: + extensions: + health_check: {} + zpages: {} + exporters: + zipkin: + endpoint: "http://somezipkin.target.com:9411/api/v2/spans" # Replace with a real endpoint. + jaeger: + endpoint: "somejaegergrpc.target.com:14250" # Replace with a real endpoint. + insecure: true + service: + extensions: [health_check, zpages] + pipelines: + traces/1: + receivers: [otlp, zipkin] + processors: [memory_limiter, batch, queued_retry] + exporters: [zipkin] + traces/2: + receivers: [otlp, jaeger] + processors: [memory_limiter, batch, queued_retry] + exporters: [jaeger] +--- +apiVersion: v1 +kind: Service +metadata: + name: otel-collector + labels: + app: opentelemetry + component: otel-collector +spec: + ports: + - name: otlp # Default endpoint for OpenTelemetry receiver. + port: 55680 + protocol: TCP + targetPort: 55680 + - name: jaeger-grpc # Default endpoing for Jaeger gRPC receiver + port: 14250 + - name: jaeger-thrift-http # Default endpoint for Jaeger HTTP receiver. + port: 14268 + - name: zipkin # Default endpoint for Zipkin receiver. + port: 9411 + - name: metrics # Default endpoint for querying metrics. + port: 8888 + selector: + component: otel-collector +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: otel-collector + labels: + app: opentelemetry + component: otel-collector +spec: + selector: + matchLabels: + app: opentelemetry + component: otel-collector + minReadySeconds: 5 + progressDeadlineSeconds: 120 + replicas: 1 #TODO - adjust this to your own requirements + template: + metadata: + labels: + app: opentelemetry + component: otel-collector + spec: + containers: + - command: + - "/otelcol" + - "--config=/conf/otel-collector-config.yaml" +# Memory Ballast size should be max 1/3 to 1/2 of memory. + - "--mem-ballast-size-mib=683" + image: otel/opentelemetry-collector-dev:latest + name: otel-collector + resources: + limits: + cpu: 1 + memory: 2Gi + requests: + cpu: 200m + memory: 400Mi + ports: + - containerPort: 55679 # Default endpoint for ZPages. + - containerPort: 55680 # Default endpoint for OpenTelemetry receiver. + - containerPort: 14250 # Default endpoint for Jaeger HTTP receiver. + - containerPort: 14268 # Default endpoint for Jaeger HTTP receiver. + - containerPort: 9411 # Default endpoint for Zipkin receiver. + - containerPort: 8888 # Default endpoint for querying metrics. + volumeMounts: + - name: otel-collector-config-vol + mountPath: /conf +# - name: otel-collector-secrets +# mountPath: /secrets + livenessProbe: + httpGet: + path: / + port: 13133 # Health Check extension default port. + readinessProbe: + httpGet: + path: / + port: 13133 # Health Check extension default port. + volumes: + - configMap: + name: otel-collector-conf + items: + - key: otel-collector-config + path: otel-collector-config.yaml + name: otel-collector-config-vol +# - secret: +# name: otel-collector-secrets +# items: +# - key: cert.pem +# path: cert.pem +# - key: key.pem +# path: key.pem diff --git a/internal/otel_collector/examples/local/otel-config.yaml b/internal/otel_collector/examples/local/otel-config.yaml new file mode 100644 index 00000000000..c269b4179c4 --- /dev/null +++ b/internal/otel_collector/examples/local/otel-config.yaml @@ -0,0 +1,55 @@ +extensions: + health_check: + pprof: + endpoint: 0.0.0.0:1777 + zpages: + endpoint: 0.0.0.0:55679 + +receivers: + otlp: + protocols: + grpc: + http: + + opencensus: + + jaeger: + protocols: + grpc: + thrift_binary: + thrift_compact: + thrift_http: + + zipkin: + + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + +processors: + batch: + +exporters: + logging: + logLevel: debug + +service: + + pipelines: + + traces: + receivers: [otlp, opencensus, jaeger, zipkin] + processors: [batch] + exporters: [logging] + + metrics: + receivers: [otlp, opencensus, prometheus] + processors: [batch] + exporters: [logging] + + extensions: [health_check, pprof, zpages] diff --git a/internal/otel_collector/exporter/README.md b/internal/otel_collector/exporter/README.md new file mode 100644 index 00000000000..a2e09f98dd2 --- /dev/null +++ b/internal/otel_collector/exporter/README.md @@ -0,0 +1,109 @@ +# General Information + +An exporter is how data gets sent to different systems/back-ends. Generally, an +exporter translates the internal format into another defined format. + +Available trace exporters (sorted alphabetically): + +- [Jaeger](jaegerexporter/README.md) +- [Kafka](kafkaexporter/README.md) +- [OpenCensus](opencensusexporter/README.md) +- [OTLP gRPC](otlpexporter/README.md) +- [OTLP HTTP](otlphttpexporter/README.md) +- [Zipkin](zipkinexporter/README.md) + +Available metric exporters (sorted alphabetically): + +- [OpenCensus](opencensusexporter/README.md) +- [OTLP gRPC](otlpexporter/README.md) +- [OTLP HTTP](otlphttpexporter/README.md) +- [Prometheus](prometheusexporter/README.md) +- [Prometheus Remote Write](prometheusremotewriteexporter/README.md) + +Available log exporters (sorted alphabetically): + +- [OTLP gRPC](otlpexporter/README.md) +- [OTLP HTTP](otlphttpexporter/README.md) + +Available local exporters (sorted alphabetically): + +- [File](fileexporter/README.md) +- [Logging](loggingexporter/README.md) + +The [contrib +repository](https://github.com/open-telemetry/opentelemetry-collector-contrib) +has more exporters available in its builds. + +## Configuring Exporters + +Exporters are configured via YAML under the top-level `exporters` tag. + +The following is a sample configuration for the `exampleexporter`. + +```yaml +exporters: + # Exporter 1. + # : + exampleexporter: + # : + endpoint: 1.2.3.4:8080 + # ... + # Exporter 2. + # /: + exampleexporter/settings: + # : + endpoint: 0.0.0.0:9211 +``` + +An exporter instance is referenced by its full name in other parts of the config, +such as in pipelines. A full name consists of the exporter type, '/' and the +name appended to the exporter type in the configuration. All exporter full names +must be unique. + +For the example above: + +- Exporter 1 has full name `exampleexporter`. +- Exporter 2 has full name `exampleexporter/settings`. + +Exporters are enabled upon being added to a pipeline. For example: + +```yaml +service: + pipelines: + # Valid pipelines are: traces, metrics or logs + # Trace pipeline 1. + traces: + receivers: [examplereceiver] + processors: [] + exporters: [exampleexporter, exampleexporter/settings] + # Trace pipeline 2. + traces/another: + receivers: [examplereceiver] + processors: [] + exporters: [exampleexporter, exampleexporter/settings] +``` + +## Data Ownership + +When multiple exporters are configured to send the same data (e.g. by configuring multiple +exporters for the same pipeline) the exporters will have a shared access to the data. +Exporters get access to this shared data when `ConsumeTraceData`/`ConsumeMetricsData` +function is called. Exporters MUST NOT modify the `TraceData`/`MetricsData` argument of +these functions. If the exporter needs to modify the data while performing the exporting +the exporter can clone the data and perform the modification on the clone or use a +copy-on-write approach for individual sub-parts of `TraceData`/`MetricsData` argument. +Any approach that does not mutate the original `TraceData`/`MetricsData` argument +(including referenced data, such as `Node`, `Resource`, `Spans`, etc) is allowed. + +## Proxy Support + +Beyond standard YAML configuration as outlined in the individual READMEs above, +exporters that leverage the net/http package (all do today) also respect the +following proxy environment variables: + +- HTTP_PROXY +- HTTPS_PROXY +- NO_PROXY + +If set at Collector start time then exporters, regardless of protocol, +will or will not proxy traffic as defined by these environment variables. diff --git a/internal/otel_collector/exporter/doc.go b/internal/otel_collector/exporter/doc.go new file mode 100644 index 00000000000..15c7e94732d --- /dev/null +++ b/internal/otel_collector/exporter/doc.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package exporter contains implementations of Exporter components. +// +// To implement a custom exporter you will need to implement component.ExporterFactory +// interface and component.Exporter interface. +// +// To make the custom exporter part of the Collector build the factory must be added +// to defaultcomponents.Components() function. +package exporter diff --git a/internal/otel_collector/exporter/exporterhelper/README.md b/internal/otel_collector/exporter/exporterhelper/README.md new file mode 100644 index 00000000000..1b1bdc42639 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/README.md @@ -0,0 +1,28 @@ +# Exporter Helper + +This is a helper exporter that other exporters can depend on. Today, it +primarily offers queued retries and resource attributes to metric labels conversion. + +> :warning: This exporter should not be added to a service pipeline. + +## Configuration + +The following configuration options can be modified: + +- `retry_on_failure` + - `enabled` (default = true) + - `initial_interval` (default = 5s): Time to wait after the first failure before retrying; ignored if `enabled` is `false` + - `max_interval` (default = 30s): Is the upper bound on backoff; ignored if `enabled` is `false` + - `max_elapsed_time` (default = 120s): Is the maximum amount of time spent trying to send a batch; ignored if `enabled` is `false` +- `sending_queue` + - `enabled` (default = true) + - `num_consumers` (default = 10): Number of consumers that dequeue batches; ignored if `enabled` is `false` + - `queue_size` (default = 5000): Maximum number of batches kept in memory before data; ignored if `enabled` is `false`; + User should calculate this as `num_seconds * requests_per_second` where: + - `num_seconds` is the number of seconds to buffer in case of a backend outage + - `requests_per_second` is the average number of requests per seconds. +- `resource_to_telemetry_conversion` + - `enabled` (default = false): If `enabled` is `true`, all the resource attributes will be converted to metric labels by default. +- `timeout` (default = 5s): Time to wait per individual attempt to send data to a backend. + +The full list of settings exposed for this helper exporter are documented [here](factory.go). diff --git a/internal/otel_collector/exporter/exporterhelper/common.go b/internal/otel_collector/exporter/exporterhelper/common.go new file mode 100644 index 00000000000..08a79ae87c5 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/common.go @@ -0,0 +1,223 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + "time" + + "go.opencensus.io/trace" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenthelper" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumererror" +) + +var ( + okStatus = trace.Status{Code: trace.StatusCodeOK} +) + +// ComponentSettings for timeout. The timeout applies to individual attempts to send data to the backend. +type TimeoutSettings struct { + // Timeout is the timeout for every attempt to send data to the backend. + Timeout time.Duration `mapstructure:"timeout"` +} + +// DefaultTimeoutSettings returns the default settings for TimeoutSettings. +func DefaultTimeoutSettings() TimeoutSettings { + return TimeoutSettings{ + Timeout: 5 * time.Second, + } +} + +// request is an abstraction of an individual request (batch of data) independent of the type of the data (traces, metrics, logs). +type request interface { + // context returns the Context of the requests. + context() context.Context + // setContext updates the Context of the requests. + setContext(context.Context) + export(ctx context.Context) (int, error) + // Returns a new request that contains the items left to be sent. + onPartialError(consumererror.PartialError) request + // Returns the count of spans/metric points or log records. + count() int +} + +// requestSender is an abstraction of a sender for a request independent of the type of the data (traces, metrics, logs). +type requestSender interface { + send(req request) (int, error) +} + +// baseRequest is a base implementation for the request. +type baseRequest struct { + ctx context.Context +} + +func (req *baseRequest) context() context.Context { + return req.ctx +} + +func (req *baseRequest) setContext(ctx context.Context) { + req.ctx = ctx +} + +// baseSettings represents all the options that users can configure. +type baseSettings struct { + *componenthelper.ComponentSettings + TimeoutSettings + QueueSettings + RetrySettings + ResourceToTelemetrySettings +} + +// fromOptions returns the internal options starting from the default and applying all configured options. +func fromOptions(options []Option) *baseSettings { + // Start from the default options: + opts := &baseSettings{ + ComponentSettings: componenthelper.DefaultComponentSettings(), + TimeoutSettings: DefaultTimeoutSettings(), + // TODO: Enable queuing by default (call DefaultQueueSettings) + QueueSettings: QueueSettings{Enabled: false}, + // TODO: Enable retry by default (call DefaultRetrySettings) + RetrySettings: RetrySettings{Enabled: false}, + ResourceToTelemetrySettings: defaultResourceToTelemetrySettings(), + } + + for _, op := range options { + op(opts) + } + + return opts +} + +// Option apply changes to baseSettings. +type Option func(*baseSettings) + +// WithShutdown overrides the default Shutdown function for an exporter. +// The default shutdown function does nothing and always returns nil. +func WithShutdown(shutdown componenthelper.Shutdown) Option { + return func(o *baseSettings) { + o.Shutdown = shutdown + } +} + +// WithStart overrides the default Start function for an exporter. +// The default shutdown function does nothing and always returns nil. +func WithStart(start componenthelper.Start) Option { + return func(o *baseSettings) { + o.Start = start + } +} + +// WithTimeout overrides the default TimeoutSettings for an exporter. +// The default TimeoutSettings is 5 seconds. +func WithTimeout(timeoutSettings TimeoutSettings) Option { + return func(o *baseSettings) { + o.TimeoutSettings = timeoutSettings + } +} + +// WithRetry overrides the default RetrySettings for an exporter. +// The default RetrySettings is to disable retries. +func WithRetry(retrySettings RetrySettings) Option { + return func(o *baseSettings) { + o.RetrySettings = retrySettings + } +} + +// WithQueue overrides the default QueueSettings for an exporter. +// The default QueueSettings is to disable queueing. +func WithQueue(queueSettings QueueSettings) Option { + return func(o *baseSettings) { + o.QueueSettings = queueSettings + } +} + +// WithResourceToTelemetryConversion overrides the default ResourceToTelemetrySettings for an exporter. +// The default ResourceToTelemetrySettings is to disable resource attributes to metric labels conversion. +func WithResourceToTelemetryConversion(resourceToTelemetrySettings ResourceToTelemetrySettings) Option { + return func(o *baseSettings) { + o.ResourceToTelemetrySettings = resourceToTelemetrySettings + } +} + +// baseExporter contains common fields between different exporter types. +type baseExporter struct { + component.Component + cfg configmodels.Exporter + sender requestSender + qrSender *queuedRetrySender + convertResourceToTelemetry bool +} + +func newBaseExporter(cfg configmodels.Exporter, logger *zap.Logger, options ...Option) *baseExporter { + bs := fromOptions(options) + be := &baseExporter{ + Component: componenthelper.NewComponent(bs.ComponentSettings), + cfg: cfg, + convertResourceToTelemetry: bs.ResourceToTelemetrySettings.Enabled, + } + + be.qrSender = newQueuedRetrySender(cfg.Name(), bs.QueueSettings, bs.RetrySettings, &timeoutSender{cfg: bs.TimeoutSettings}, logger) + be.sender = be.qrSender + + return be +} + +// wrapConsumerSender wraps the consumer sender (the sender that uses retries and timeout) with the given wrapper. +// This can be used to wrap with observability (create spans, record metrics) the consumer sender. +func (be *baseExporter) wrapConsumerSender(f func(consumer requestSender) requestSender) { + be.qrSender.consumerSender = f(be.qrSender.consumerSender) +} + +// Start all senders and exporter and is invoked during service start. +func (be *baseExporter) Start(ctx context.Context, host component.Host) error { + // First start the wrapped exporter. + if err := be.Component.Start(ctx, host); err != nil { + return err + } + + // If no error then start the queuedRetrySender. + be.qrSender.start() + return nil +} + +// Shutdown all senders and exporter and is invoked during service shutdown. +func (be *baseExporter) Shutdown(ctx context.Context) error { + // First shutdown the queued retry sender + be.qrSender.shutdown() + // Last shutdown the wrapped exporter itself. + return be.Component.Shutdown(ctx) +} + +// timeoutSender is a request sender that adds a `timeout` to every request that passes this sender. +type timeoutSender struct { + cfg TimeoutSettings +} + +// send implements the requestSender interface +func (ts *timeoutSender) send(req request) (int, error) { + // Intentionally don't overwrite the context inside the request, because in case of retries deadline will not be + // updated because this deadline most likely is before the next one. + ctx := req.context() + if ts.cfg.Timeout > 0 { + var cancelFunc func() + ctx, cancelFunc = context.WithTimeout(req.context(), ts.cfg.Timeout) + defer cancelFunc() + } + return req.export(ctx) +} diff --git a/internal/otel_collector/exporter/exporterhelper/common_test.go b/internal/otel_collector/exporter/exporterhelper/common_test.go new file mode 100644 index 00000000000..23718d4225c --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/common_test.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package exporterhelper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.opencensus.io/trace" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" +) + +var defaultExporterCfg = &configmodels.ExporterSettings{ + TypeVal: "test", + NameVal: "test", +} + +func TestErrorToStatus(t *testing.T) { + require.Equal(t, okStatus, errToStatus(nil)) + require.Equal(t, trace.Status{Code: trace.StatusCodeUnknown, Message: "my_error"}, errToStatus(errors.New("my_error"))) +} + +func TestBaseExporter(t *testing.T) { + be := newBaseExporter(defaultExporterCfg, zap.NewNop()) + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, be.Shutdown(context.Background())) +} + +func TestBaseExporterWithOptions(t *testing.T) { + want := errors.New("my error") + be := newBaseExporter( + defaultExporterCfg, + zap.NewNop(), + WithStart(func(ctx context.Context, host component.Host) error { return want }), + WithShutdown(func(ctx context.Context) error { return want }), + WithResourceToTelemetryConversion(defaultResourceToTelemetrySettings()), + WithTimeout(DefaultTimeoutSettings()), + ) + require.Equal(t, want, be.Start(context.Background(), componenttest.NewNopHost())) + require.Equal(t, want, be.Shutdown(context.Background())) +} + +func errToStatus(err error) trace.Status { + if err != nil { + return trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()} + } + return okStatus +} diff --git a/internal/otel_collector/exporter/exporterhelper/constants.go b/internal/otel_collector/exporter/exporterhelper/constants.go new file mode 100644 index 00000000000..2fb7511a438 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/constants.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "errors" +) + +var ( + // errNilConfig is returned when an empty name is given. + errNilConfig = errors.New("nil config") + // errNilLogger is returned when a logger is nil + errNilLogger = errors.New("nil logger") + // errNilPushTraceData is returned when a nil PushTraces is given. + errNilPushTraceData = errors.New("nil PushTraces") + // errNilPushMetricsData is returned when a nil PushMetrics is given. + errNilPushMetricsData = errors.New("nil PushMetrics") + // errNilPushLogsData is returned when a nil PushLogs is given. + errNilPushLogsData = errors.New("nil PushLogs") +) diff --git a/internal/otel_collector/exporter/exporterhelper/factory.go b/internal/otel_collector/exporter/exporterhelper/factory.go new file mode 100644 index 00000000000..7b55a60ec2d --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/factory.go @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" +) + +// FactoryOption apply changes to ExporterOptions. +type FactoryOption func(o *factory) + +// CreateDefaultConfig is the equivalent of component.ExporterFactory.CreateDefaultConfig() +type CreateDefaultConfig func() configmodels.Exporter + +// CreateTraceExporter is the equivalent of component.ExporterFactory.CreateTracesExporter() +type CreateTraceExporter func(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.TracesExporter, error) + +// CreateMetricsExporter is the equivalent of component.ExporterFactory.CreateMetricsExporter() +type CreateMetricsExporter func(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.MetricsExporter, error) + +// CreateMetricsExporter is the equivalent of component.ExporterFactory.CreateLogsExporter() +type CreateLogsExporter func(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.LogsExporter, error) + +type factory struct { + cfgType configmodels.Type + customUnmarshaler component.CustomUnmarshaler + createDefaultConfig CreateDefaultConfig + createTraceExporter CreateTraceExporter + createMetricsExporter CreateMetricsExporter + createLogsExporter CreateLogsExporter +} + +// WithTraces overrides the default "error not supported" implementation for CreateTracesReceiver. +func WithTraces(createTraceExporter CreateTraceExporter) FactoryOption { + return func(o *factory) { + o.createTraceExporter = createTraceExporter + } +} + +// WithMetrics overrides the default "error not supported" implementation for CreateMetricsReceiver. +func WithMetrics(createMetricsExporter CreateMetricsExporter) FactoryOption { + return func(o *factory) { + o.createMetricsExporter = createMetricsExporter + } +} + +// WithLogs overrides the default "error not supported" implementation for CreateLogsReceiver. +func WithLogs(createLogsExporter CreateLogsExporter) FactoryOption { + return func(o *factory) { + o.createLogsExporter = createLogsExporter + } +} + +// WithCustomUnmarshaler implements component.ConfigUnmarshaler. +func WithCustomUnmarshaler(customUnmarshaler component.CustomUnmarshaler) FactoryOption { + return func(o *factory) { + o.customUnmarshaler = customUnmarshaler + } +} + +// NewFactory returns a component.ExporterFactory. +func NewFactory( + cfgType configmodels.Type, + createDefaultConfig CreateDefaultConfig, + options ...FactoryOption) component.ExporterFactory { + f := &factory{ + cfgType: cfgType, + createDefaultConfig: createDefaultConfig, + } + for _, opt := range options { + opt(f) + } + var ret component.ExporterFactory + if f.customUnmarshaler != nil { + ret = &factoryWithUnmarshaler{f} + } else { + ret = f + } + return ret +} + +// Type gets the type of the Exporter config created by this factory. +func (f *factory) Type() configmodels.Type { + return f.cfgType +} + +// CreateDefaultConfig creates the default configuration for processor. +func (f *factory) CreateDefaultConfig() configmodels.Exporter { + return f.createDefaultConfig() +} + +// CreateTraceExporter creates a component.TracesExporter based on this config. +func (f *factory) CreateTracesExporter( + ctx context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter) (component.TracesExporter, error) { + if f.createTraceExporter != nil { + return f.createTraceExporter(ctx, params, cfg) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsExporter creates a consumer.MetricsConsumer based on this config. +func (f *factory) CreateMetricsExporter( + ctx context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter) (component.MetricsExporter, error) { + if f.createMetricsExporter != nil { + return f.createMetricsExporter(ctx, params, cfg) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateLogsExporter creates a metrics processor based on this config. +func (f *factory) CreateLogsExporter( + ctx context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.LogsExporter, error) { + if f.createLogsExporter != nil { + return f.createLogsExporter(ctx, params, cfg) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +var _ component.ConfigUnmarshaler = (*factoryWithUnmarshaler)(nil) + +type factoryWithUnmarshaler struct { + *factory +} + +// Unmarshal un-marshals the config using the provided custom unmarshaler. +func (f *factoryWithUnmarshaler) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error { + return f.customUnmarshaler(componentViperSection, intoCfg) +} diff --git a/internal/otel_collector/exporter/exporterhelper/factory_test.go b/internal/otel_collector/exporter/exporterhelper/factory_test.go new file mode 100644 index 00000000000..b4694a21853 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/factory_test.go @@ -0,0 +1,112 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + "errors" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/pdata" +) + +const typeStr = "test" + +var ( + defaultCfg = &configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + nopTracesExporter, _ = NewTraceExporter(defaultCfg, zap.NewNop(), func(ctx context.Context, td pdata.Traces) (droppedSpans int, err error) { + return 0, nil + }) + nopMetricsExporter, _ = NewMetricsExporter(defaultCfg, zap.NewNop(), func(ctx context.Context, md pdata.Metrics) (droppedTimeSeries int, err error) { + return 0, nil + }) + nopLogsExporter, _ = NewLogsExporter(defaultCfg, zap.NewNop(), func(ctx context.Context, md pdata.Logs) (droppedTimeSeries int, err error) { + return 0, nil + }) +) + +func TestNewFactory(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + _, ok := factory.(component.ConfigUnmarshaler) + assert.False(t, ok) + _, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, defaultCfg) + assert.Equal(t, configerror.ErrDataTypeIsNotSupported, err) + _, err = factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, defaultCfg) + assert.Equal(t, configerror.ErrDataTypeIsNotSupported, err) + _, err = factory.CreateLogsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, defaultCfg) + assert.Equal(t, configerror.ErrDataTypeIsNotSupported, err) +} + +func TestNewFactory_WithConstructors(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig, + WithTraces(createTraceExporter), + WithMetrics(createMetricsExporter), + WithLogs(createLogsExporter), + WithCustomUnmarshaler(customUnmarshaler)) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + + fu, ok := factory.(component.ConfigUnmarshaler) + assert.True(t, ok) + assert.Equal(t, errors.New("my error"), fu.Unmarshal(nil, nil)) + + te, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, defaultCfg) + assert.NoError(t, err) + assert.Same(t, nopTracesExporter, te) + + me, err := factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, defaultCfg) + assert.NoError(t, err) + assert.Same(t, nopMetricsExporter, me) + + le, err := factory.CreateLogsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, defaultCfg) + assert.NoError(t, err) + assert.Same(t, nopLogsExporter, le) +} + +func defaultConfig() configmodels.Exporter { + return defaultCfg +} + +func createTraceExporter(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.TracesExporter, error) { + return nopTracesExporter, nil +} + +func createMetricsExporter(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.MetricsExporter, error) { + return nopMetricsExporter, nil +} + +func createLogsExporter(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.LogsExporter, error) { + return nopLogsExporter, nil +} + +func customUnmarshaler(*viper.Viper, interface{}) error { + return errors.New("my error") +} diff --git a/internal/otel_collector/exporter/exporterhelper/logshelper.go b/internal/otel_collector/exporter/exporterhelper/logshelper.go new file mode 100644 index 00000000000..cb79d2b0fd2 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/logshelper.go @@ -0,0 +1,114 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" +) + +// PushLogs is a helper function that is similar to ConsumeLogs but also returns +// the number of dropped logs. +type PushLogs func(ctx context.Context, md pdata.Logs) (droppedTimeSeries int, err error) + +type logsRequest struct { + baseRequest + ld pdata.Logs + pusher PushLogs +} + +func newLogsRequest(ctx context.Context, ld pdata.Logs, pusher PushLogs) request { + return &logsRequest{ + baseRequest: baseRequest{ctx: ctx}, + ld: ld, + pusher: pusher, + } +} + +func (req *logsRequest) onPartialError(partialErr consumererror.PartialError) request { + return newLogsRequest(req.ctx, partialErr.GetLogs(), req.pusher) +} + +func (req *logsRequest) export(ctx context.Context) (int, error) { + return req.pusher(ctx, req.ld) +} + +func (req *logsRequest) count() int { + return req.ld.LogRecordCount() +} + +type logsExporter struct { + *baseExporter + pusher PushLogs +} + +func (lexp *logsExporter) ConsumeLogs(ctx context.Context, ld pdata.Logs) error { + exporterCtx := obsreport.ExporterContext(ctx, lexp.cfg.Name()) + _, err := lexp.sender.send(newLogsRequest(exporterCtx, ld, lexp.pusher)) + return err +} + +// NewLogsExporter creates an LogsExporter that records observability metrics and wraps every request with a Span. +func NewLogsExporter( + cfg configmodels.Exporter, + logger *zap.Logger, + pusher PushLogs, + options ...Option, +) (component.LogsExporter, error) { + if cfg == nil { + return nil, errNilConfig + } + + if logger == nil { + return nil, errNilLogger + } + + if pusher == nil { + return nil, errNilPushLogsData + } + + be := newBaseExporter(cfg, logger, options...) + be.wrapConsumerSender(func(nextSender requestSender) requestSender { + return &logsExporterWithObservability{ + obsrep: obsreport.NewExporterObsReport(configtelemetry.GetMetricsLevelFlagValue(), cfg.Name()), + nextSender: nextSender, + } + }) + + return &logsExporter{ + baseExporter: be, + pusher: pusher, + }, nil +} + +type logsExporterWithObservability struct { + obsrep *obsreport.ExporterObsReport + nextSender requestSender +} + +func (lewo *logsExporterWithObservability) send(req request) (int, error) { + req.setContext(lewo.obsrep.StartLogsExportOp(req.context())) + numDroppedLogs, err := lewo.nextSender.send(req) + lewo.obsrep.EndLogsExportOp(req.context(), req.count(), err) + return numDroppedLogs, err +} diff --git a/internal/otel_collector/exporter/exporterhelper/logshelper_test.go b/internal/otel_collector/exporter/exporterhelper/logshelper_test.go new file mode 100644 index 00000000000..6169225ca6d --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/logshelper_test.go @@ -0,0 +1,232 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package exporterhelper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/trace" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +const ( + fakeLogsExporterType = "fake_logs_exporter" + fakeLogsExporterName = "fake_logs_exporter/with_name" + fakeLogsParentSpanName = "fake_logs_parent_span_name" +) + +var ( + fakeLogsExporterConfig = &configmodels.ExporterSettings{ + TypeVal: fakeLogsExporterType, + NameVal: fakeLogsExporterName, + } +) + +func TestLogsRequest(t *testing.T) { + lr := newLogsRequest(context.Background(), testdata.GenerateLogDataOneLog(), nil) + + partialErr := consumererror.PartialLogsError(errors.New("some error"), testdata.GenerateLogDataEmpty()) + assert.EqualValues( + t, + newLogsRequest(context.Background(), testdata.GenerateLogDataEmpty(), nil), + lr.onPartialError(partialErr.(consumererror.PartialError)), + ) +} + +func TestLogsExporter_InvalidName(t *testing.T) { + le, err := NewLogsExporter(nil, zap.NewNop(), newPushLogsData(0, nil)) + require.Nil(t, le) + require.Equal(t, errNilConfig, err) +} + +func TestLogsExporter_NilLogger(t *testing.T) { + le, err := NewLogsExporter(fakeLogsExporterConfig, nil, newPushLogsData(0, nil)) + require.Nil(t, le) + require.Equal(t, errNilLogger, err) +} + +func TestLogsExporter_NilPushLogsData(t *testing.T) { + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), nil) + require.Nil(t, le) + require.Equal(t, errNilPushLogsData, err) +} + +func TestLogsExporter_Default(t *testing.T) { + ld := testdata.GenerateLogDataEmpty() + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, nil)) + assert.NotNil(t, le) + assert.NoError(t, err) + + assert.Nil(t, le.ConsumeLogs(context.Background(), ld)) + assert.Nil(t, le.Shutdown(context.Background())) +} + +func TestLogsExporter_Default_ReturnError(t *testing.T) { + ld := testdata.GenerateLogDataEmpty() + want := errors.New("my_error") + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, want)) + require.Nil(t, err) + require.NotNil(t, le) + require.Equal(t, want, le.ConsumeLogs(context.Background(), ld)) +} + +func TestLogsExporter_WithRecordLogs(t *testing.T) { + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, nil)) + require.Nil(t, err) + require.NotNil(t, le) + + checkRecordedMetricsForLogsExporter(t, le, nil) +} + +func TestLogsExporter_WithRecordLogs_NonZeroDropped(t *testing.T) { + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(1, nil)) + require.Nil(t, err) + require.NotNil(t, le) + + checkRecordedMetricsForLogsExporter(t, le, nil) +} + +func TestLogsExporter_WithRecordLogs_ReturnError(t *testing.T) { + want := errors.New("my_error") + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, want)) + require.Nil(t, err) + require.NotNil(t, le) + + checkRecordedMetricsForLogsExporter(t, le, want) +} + +func TestLogsExporter_WithSpan(t *testing.T) { + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, nil)) + require.Nil(t, err) + require.NotNil(t, le) + checkWrapSpanForLogsExporter(t, le, nil, 1) +} + +func TestLogsExporter_WithSpan_NonZeroDropped(t *testing.T) { + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(1, nil)) + require.Nil(t, err) + require.NotNil(t, le) + checkWrapSpanForLogsExporter(t, le, nil, 1) +} + +func TestLogsExporter_WithSpan_ReturnError(t *testing.T) { + want := errors.New("my_error") + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, want)) + require.Nil(t, err) + require.NotNil(t, le) + checkWrapSpanForLogsExporter(t, le, want, 1) +} + +func TestLogsExporter_WithShutdown(t *testing.T) { + shutdownCalled := false + shutdown := func(context.Context) error { shutdownCalled = true; return nil } + + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, nil), WithShutdown(shutdown)) + assert.NotNil(t, le) + assert.NoError(t, err) + + assert.Nil(t, le.Shutdown(context.Background())) + assert.True(t, shutdownCalled) +} + +func TestLogsExporter_WithShutdown_ReturnError(t *testing.T) { + want := errors.New("my_error") + shutdownErr := func(context.Context) error { return want } + + le, err := NewLogsExporter(fakeLogsExporterConfig, zap.NewNop(), newPushLogsData(0, nil), WithShutdown(shutdownErr)) + assert.NotNil(t, le) + assert.NoError(t, err) + + assert.Equal(t, le.Shutdown(context.Background()), want) +} + +func newPushLogsData(droppedTimeSeries int, retError error) PushLogs { + return func(ctx context.Context, td pdata.Logs) (int, error) { + return droppedTimeSeries, retError + } +} + +func checkRecordedMetricsForLogsExporter(t *testing.T, le component.LogsExporter, wantError error) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ld := testdata.GenerateLogDataTwoLogsSameResource() + const numBatches = 7 + for i := 0; i < numBatches; i++ { + require.Equal(t, wantError, le.ConsumeLogs(context.Background(), ld)) + } + + // TODO: When the new metrics correctly count partial dropped fix this. + if wantError != nil { + obsreporttest.CheckExporterLogsViews(t, fakeLogsExporterName, 0, int64(numBatches*ld.LogRecordCount())) + } else { + obsreporttest.CheckExporterLogsViews(t, fakeLogsExporterName, int64(numBatches*ld.LogRecordCount()), 0) + } +} + +func generateLogsTraffic(t *testing.T, le component.LogsExporter, numRequests int, wantError error) { + ld := testdata.GenerateLogDataOneLog() + ctx, span := trace.StartSpan(context.Background(), fakeLogsParentSpanName, trace.WithSampler(trace.AlwaysSample())) + defer span.End() + for i := 0; i < numRequests; i++ { + require.Equal(t, wantError, le.ConsumeLogs(ctx, ld)) + } +} + +func checkWrapSpanForLogsExporter(t *testing.T, le component.LogsExporter, wantError error, numLogRecords int64) { + ocSpansSaver := new(testOCTraceExporter) + trace.RegisterExporter(ocSpansSaver) + defer trace.UnregisterExporter(ocSpansSaver) + + const numRequests = 5 + generateLogsTraffic(t, le, numRequests, wantError) + + // Inspection time! + ocSpansSaver.mu.Lock() + defer ocSpansSaver.mu.Unlock() + + require.NotEqual(t, 0, len(ocSpansSaver.spanData), "No exported span data") + + gotSpanData := ocSpansSaver.spanData + require.Equal(t, numRequests+1, len(gotSpanData)) + + parentSpan := gotSpanData[numRequests] + require.Equalf(t, fakeLogsParentSpanName, parentSpan.Name, "SpanData %v", parentSpan) + for _, sd := range gotSpanData[:numRequests] { + require.Equalf(t, parentSpan.SpanContext.SpanID, sd.ParentSpanID, "Exporter span not a child\nSpanData %v", sd) + require.Equalf(t, errToStatus(wantError), sd.Status, "SpanData %v", sd) + + sentLogRecords := numLogRecords + var failedToSendLogRecords int64 + if wantError != nil { + sentLogRecords = 0 + failedToSendLogRecords = numLogRecords + } + require.Equalf(t, sentLogRecords, sd.Attributes[obsreport.SentLogRecordsKey], "SpanData %v", sd) + require.Equalf(t, failedToSendLogRecords, sd.Attributes[obsreport.FailedToSendLogRecordsKey], "SpanData %v", sd) + } +} diff --git a/internal/otel_collector/exporter/exporterhelper/metricshelper.go b/internal/otel_collector/exporter/exporterhelper/metricshelper.go new file mode 100644 index 00000000000..013d4672ef7 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/metricshelper.go @@ -0,0 +1,126 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" +) + +// PushMetrics is a helper function that is similar to ConsumeMetrics but also returns +// the number of dropped metrics. +type PushMetrics func(ctx context.Context, md pdata.Metrics) (droppedTimeSeries int, err error) + +type metricsRequest struct { + baseRequest + md pdata.Metrics + pusher PushMetrics +} + +func newMetricsRequest(ctx context.Context, md pdata.Metrics, pusher PushMetrics) request { + return &metricsRequest{ + baseRequest: baseRequest{ctx: ctx}, + md: md, + pusher: pusher, + } +} + +func (req *metricsRequest) onPartialError(partialErr consumererror.PartialError) request { + return newMetricsRequest(req.ctx, partialErr.GetMetrics(), req.pusher) +} + +func (req *metricsRequest) export(ctx context.Context) (int, error) { + return req.pusher(ctx, req.md) +} + +func (req *metricsRequest) count() int { + _, numPoints := req.md.MetricAndDataPointCount() + return numPoints +} + +type metricsExporter struct { + *baseExporter + pusher PushMetrics +} + +func (mexp *metricsExporter) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + if mexp.baseExporter.convertResourceToTelemetry { + md = convertResourceToLabels(md) + } + exporterCtx := obsreport.ExporterContext(ctx, mexp.cfg.Name()) + req := newMetricsRequest(exporterCtx, md, mexp.pusher) + _, err := mexp.sender.send(req) + return err +} + +// NewMetricsExporter creates an MetricsExporter that records observability metrics and wraps every request with a Span. +func NewMetricsExporter( + cfg configmodels.Exporter, + logger *zap.Logger, + pusher PushMetrics, + options ...Option, +) (component.MetricsExporter, error) { + if cfg == nil { + return nil, errNilConfig + } + + if logger == nil { + return nil, errNilLogger + } + + if pusher == nil { + return nil, errNilPushMetricsData + } + + be := newBaseExporter(cfg, logger, options...) + be.wrapConsumerSender(func(nextSender requestSender) requestSender { + return &metricsSenderWithObservability{ + obsrep: obsreport.NewExporterObsReport(configtelemetry.GetMetricsLevelFlagValue(), cfg.Name()), + nextSender: nextSender, + } + }) + + return &metricsExporter{ + baseExporter: be, + pusher: pusher, + }, nil +} + +type metricsSenderWithObservability struct { + obsrep *obsreport.ExporterObsReport + nextSender requestSender +} + +func (mewo *metricsSenderWithObservability) send(req request) (int, error) { + req.setContext(mewo.obsrep.StartMetricsExportOp(req.context())) + _, err := mewo.nextSender.send(req) + + // TODO: this is not ideal: it should come from the next function itself. + // temporarily loading it from internal format. Once full switch is done + // to new metrics will remove this. + mReq := req.(*metricsRequest) + numReceivedMetrics, numPoints := mReq.md.MetricAndDataPointCount() + + mewo.obsrep.EndMetricsExportOp(req.context(), numPoints, err) + return numReceivedMetrics, err +} diff --git a/internal/otel_collector/exporter/exporterhelper/metricshelper_test.go b/internal/otel_collector/exporter/exporterhelper/metricshelper_test.go new file mode 100644 index 00000000000..deb37bfc60e --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/metricshelper_test.go @@ -0,0 +1,253 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package exporterhelper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/trace" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +const ( + fakeMetricsExporterType = "fake_metrics_exporter" + fakeMetricsExporterName = "fake_metrics_exporter/with_name" + fakeMetricsParentSpanName = "fake_metrics_parent_span_name" +) + +var ( + fakeMetricsExporterConfig = &configmodels.ExporterSettings{ + TypeVal: fakeMetricsExporterType, + NameVal: fakeMetricsExporterName, + } +) + +func TestMetricsRequest(t *testing.T) { + mr := newMetricsRequest(context.Background(), testdata.GenerateMetricsOneMetric(), nil) + + partialErr := consumererror.PartialMetricsError(errors.New("some error"), testdata.GenerateMetricsEmpty()) + assert.EqualValues( + t, + newMetricsRequest(context.Background(), testdata.GenerateMetricsEmpty(), nil), + mr.onPartialError(partialErr.(consumererror.PartialError)), + ) +} + +func TestMetricsExporter_InvalidName(t *testing.T) { + me, err := NewMetricsExporter(nil, zap.NewNop(), newPushMetricsData(0, nil)) + require.Nil(t, me) + require.Equal(t, errNilConfig, err) +} + +func TestMetricsExporter_NilLogger(t *testing.T) { + me, err := NewMetricsExporter(fakeMetricsExporterConfig, nil, newPushMetricsData(0, nil)) + require.Nil(t, me) + require.Equal(t, errNilLogger, err) +} + +func TestMetricsExporter_NilPushMetricsData(t *testing.T) { + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), nil) + require.Nil(t, me) + require.Equal(t, errNilPushMetricsData, err) +} + +func TestMetricsExporter_Default(t *testing.T) { + md := testdata.GenerateMetricsEmpty() + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil)) + assert.NotNil(t, me) + assert.NoError(t, err) + + assert.Nil(t, me.ConsumeMetrics(context.Background(), md)) + assert.Nil(t, me.Shutdown(context.Background())) +} + +func TestMetricsExporter_Default_ReturnError(t *testing.T) { + md := testdata.GenerateMetricsEmpty() + want := errors.New("my_error") + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, want)) + require.Nil(t, err) + require.NotNil(t, me) + require.Equal(t, want, me.ConsumeMetrics(context.Background(), md)) +} + +func TestMetricsExporter_WithRecordMetrics(t *testing.T) { + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil)) + require.Nil(t, err) + require.NotNil(t, me) + + checkRecordedMetricsForMetricsExporter(t, me, nil) +} + +func TestMetricsExporter_WithRecordMetrics_NonZeroDropped(t *testing.T) { + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(1, nil)) + require.Nil(t, err) + require.NotNil(t, me) + + checkRecordedMetricsForMetricsExporter(t, me, nil) +} + +func TestMetricsExporter_WithRecordMetrics_ReturnError(t *testing.T) { + want := errors.New("my_error") + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, want)) + require.Nil(t, err) + require.NotNil(t, me) + + checkRecordedMetricsForMetricsExporter(t, me, want) +} + +func TestMetricsExporter_WithSpan(t *testing.T) { + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil)) + require.Nil(t, err) + require.NotNil(t, me) + checkWrapSpanForMetricsExporter(t, me, nil, 1) +} + +func TestMetricsExporter_WithSpan_NonZeroDropped(t *testing.T) { + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(1, nil)) + require.Nil(t, err) + require.NotNil(t, me) + checkWrapSpanForMetricsExporter(t, me, nil, 1) +} + +func TestMetricsExporter_WithSpan_ReturnError(t *testing.T) { + want := errors.New("my_error") + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, want)) + require.Nil(t, err) + require.NotNil(t, me) + checkWrapSpanForMetricsExporter(t, me, want, 1) +} + +func TestMetricsExporter_WithShutdown(t *testing.T) { + shutdownCalled := false + shutdown := func(context.Context) error { shutdownCalled = true; return nil } + + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil), WithShutdown(shutdown)) + assert.NotNil(t, me) + assert.NoError(t, err) + + assert.Nil(t, me.Shutdown(context.Background())) + assert.True(t, shutdownCalled) +} + +func TestMetricsExporter_WithResourceToTelemetryConversionDisabled(t *testing.T) { + md := testdata.GenerateMetricsTwoMetrics() + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil), WithResourceToTelemetryConversion(defaultResourceToTelemetrySettings())) + assert.NotNil(t, me) + assert.NoError(t, err) + + assert.Nil(t, me.ConsumeMetrics(context.Background(), md)) + assert.Nil(t, me.Shutdown(context.Background())) +} + +func TestMetricsExporter_WithResourceToTelemetryConversionEbabled(t *testing.T) { + md := testdata.GenerateMetricsTwoMetrics() + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil), WithResourceToTelemetryConversion(ResourceToTelemetrySettings{Enabled: true})) + assert.NotNil(t, me) + assert.NoError(t, err) + + assert.Nil(t, me.ConsumeMetrics(context.Background(), md)) + assert.Nil(t, me.Shutdown(context.Background())) +} + +func TestMetricsExporter_WithShutdown_ReturnError(t *testing.T) { + want := errors.New("my_error") + shutdownErr := func(context.Context) error { return want } + + me, err := NewMetricsExporter(fakeMetricsExporterConfig, zap.NewNop(), newPushMetricsData(0, nil), WithShutdown(shutdownErr)) + assert.NotNil(t, me) + assert.NoError(t, err) + + assert.Equal(t, me.Shutdown(context.Background()), want) +} + +func newPushMetricsData(droppedTimeSeries int, retError error) PushMetrics { + return func(ctx context.Context, td pdata.Metrics) (int, error) { + return droppedTimeSeries, retError + } +} + +func checkRecordedMetricsForMetricsExporter(t *testing.T, me component.MetricsExporter, wantError error) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + md := testdata.GenerateMetricsTwoMetrics() + const numBatches = 7 + for i := 0; i < numBatches; i++ { + require.Equal(t, wantError, me.ConsumeMetrics(context.Background(), md)) + } + + // TODO: When the new metrics correctly count partial dropped fix this. + numPoints := int64(numBatches * md.MetricCount() * 2) /* 2 points per metric*/ + if wantError != nil { + obsreporttest.CheckExporterMetricsViews(t, fakeMetricsExporterName, 0, numPoints) + } else { + obsreporttest.CheckExporterMetricsViews(t, fakeMetricsExporterName, numPoints, 0) + } +} + +func generateMetricsTraffic(t *testing.T, me component.MetricsExporter, numRequests int, wantError error) { + md := testdata.GenerateMetricsOneMetricOneDataPoint() + ctx, span := trace.StartSpan(context.Background(), fakeMetricsParentSpanName, trace.WithSampler(trace.AlwaysSample())) + defer span.End() + for i := 0; i < numRequests; i++ { + require.Equal(t, wantError, me.ConsumeMetrics(ctx, md)) + } +} + +func checkWrapSpanForMetricsExporter(t *testing.T, me component.MetricsExporter, wantError error, numMetricPoints int64) { + ocSpansSaver := new(testOCTraceExporter) + trace.RegisterExporter(ocSpansSaver) + defer trace.UnregisterExporter(ocSpansSaver) + + const numRequests = 5 + generateMetricsTraffic(t, me, numRequests, wantError) + + // Inspection time! + ocSpansSaver.mu.Lock() + defer ocSpansSaver.mu.Unlock() + + require.NotEqual(t, 0, len(ocSpansSaver.spanData), "No exported span data") + + gotSpanData := ocSpansSaver.spanData + require.Equal(t, numRequests+1, len(gotSpanData)) + + parentSpan := gotSpanData[numRequests] + require.Equalf(t, fakeMetricsParentSpanName, parentSpan.Name, "SpanData %v", parentSpan) + for _, sd := range gotSpanData[:numRequests] { + require.Equalf(t, parentSpan.SpanContext.SpanID, sd.ParentSpanID, "Exporter span not a child\nSpanData %v", sd) + require.Equalf(t, errToStatus(wantError), sd.Status, "SpanData %v", sd) + + sentMetricPoints := numMetricPoints + var failedToSendMetricPoints int64 + if wantError != nil { + sentMetricPoints = 0 + failedToSendMetricPoints = numMetricPoints + } + require.Equalf(t, sentMetricPoints, sd.Attributes[obsreport.SentMetricPointsKey], "SpanData %v", sd) + require.Equalf(t, failedToSendMetricPoints, sd.Attributes[obsreport.FailedToSendMetricPointsKey], "SpanData %v", sd) + } +} diff --git a/internal/otel_collector/exporter/exporterhelper/queued_retry.go b/internal/otel_collector/exporter/exporterhelper/queued_retry.go new file mode 100644 index 00000000000..4fe509d7ebb --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/queued_retry.go @@ -0,0 +1,316 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/cenkalti/backoff" + "github.com/jaegertracing/jaeger/pkg/queue" + "go.opencensus.io/trace" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/obsreport" +) + +// QueueSettings defines configuration for queueing batches before sending to the consumerSender. +type QueueSettings struct { + // Enabled indicates whether to not enqueue batches before sending to the consumerSender. + Enabled bool `mapstructure:"enabled"` + // NumConsumers is the number of consumers from the queue. + NumConsumers int `mapstructure:"num_consumers"` + // QueueSize is the maximum number of batches allowed in queue at a given time. + QueueSize int `mapstructure:"queue_size"` +} + +// DefaultQueueSettings returns the default settings for QueueSettings. +func DefaultQueueSettings() QueueSettings { + return QueueSettings{ + Enabled: true, + NumConsumers: 10, + // For 5000 queue elements at 100 requests/sec gives about 50 sec of survival of destination outage. + // This is a pretty decent value for production. + // User should calculate this from the perspective of how many seconds to buffer in case of a backend outage, + // multiply that by the number of requests per seconds. + QueueSize: 5000, + } +} + +// RetrySettings defines configuration for retrying batches in case of export failure. +// The current supported strategy is exponential backoff. +type RetrySettings struct { + // Enabled indicates whether to not retry sending batches in case of export failure. + Enabled bool `mapstructure:"enabled"` + // InitialInterval the time to wait after the first failure before retrying. + InitialInterval time.Duration `mapstructure:"initial_interval"` + // MaxInterval is the upper bound on backoff interval. Once this value is reached the delay between + // consecutive retries will always be `MaxInterval`. + MaxInterval time.Duration `mapstructure:"max_interval"` + // MaxElapsedTime is the maximum amount of time (including retries) spent trying to send a request/batch. + // Once this value is reached, the data is discarded. + MaxElapsedTime time.Duration `mapstructure:"max_elapsed_time"` +} + +// DefaultRetrySettings returns the default settings for RetrySettings. +func DefaultRetrySettings() RetrySettings { + return RetrySettings{ + Enabled: true, + InitialInterval: 5 * time.Second, + MaxInterval: 30 * time.Second, + MaxElapsedTime: 5 * time.Minute, + } +} + +type queuedRetrySender struct { + cfg QueueSettings + consumerSender requestSender + queue *queue.BoundedQueue + retryStopCh chan struct{} + traceAttributes []trace.Attribute + logger *zap.Logger +} + +func createSampledLogger(logger *zap.Logger) *zap.Logger { + if logger.Core().Enabled(zapcore.DebugLevel) { + // Debugging is enabled. Don't do any sampling. + return logger + } + + // Create a logger that samples all messages to 1 per 10 seconds initially, + // and 1/100 of messages after that. + opts := zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewSamplerWithOptions( + core, + 10*time.Second, + 1, + 100, + ) + }) + return logger.WithOptions(opts) +} + +func newQueuedRetrySender(fullName string, qCfg QueueSettings, rCfg RetrySettings, nextSender requestSender, logger *zap.Logger) *queuedRetrySender { + retryStopCh := make(chan struct{}) + sampledLogger := createSampledLogger(logger) + traceAttr := trace.StringAttribute(obsreport.ExporterKey, fullName) + return &queuedRetrySender{ + cfg: qCfg, + consumerSender: &retrySender{ + traceAttribute: traceAttr, + cfg: rCfg, + nextSender: nextSender, + stopCh: retryStopCh, + logger: sampledLogger, + }, + queue: queue.NewBoundedQueue(qCfg.QueueSize, func(item interface{}) {}), + retryStopCh: retryStopCh, + traceAttributes: []trace.Attribute{traceAttr}, + logger: sampledLogger, + } +} + +// start is invoked during service startup. +func (qrs *queuedRetrySender) start() { + qrs.queue.StartConsumers(qrs.cfg.NumConsumers, func(item interface{}) { + req := item.(request) + _, _ = qrs.consumerSender.send(req) + }) +} + +// send implements the requestSender interface +func (qrs *queuedRetrySender) send(req request) (int, error) { + if !qrs.cfg.Enabled { + n, err := qrs.consumerSender.send(req) + if err != nil { + qrs.logger.Error( + "Exporting failed. Dropping data. Try enabling sending_queue to survive temporary failures.", + zap.Int("dropped_items", n), + ) + } + return n, err + } + + // Prevent cancellation and deadline to propagate to the context stored in the queue. + // The grpc/http based receivers will cancel the request context after this function returns. + req.setContext(noCancellationContext{Context: req.context()}) + + span := trace.FromContext(req.context()) + if !qrs.queue.Produce(req) { + qrs.logger.Error( + "Dropping data because sending_queue is full. Try increasing queue_size.", + zap.Int("dropped_items", req.count()), + ) + span.Annotate(qrs.traceAttributes, "Dropped item, sending_queue is full.") + return req.count(), errors.New("sending_queue is full") + } + + span.Annotate(qrs.traceAttributes, "Enqueued item.") + return 0, nil +} + +// shutdown is invoked during service shutdown. +func (qrs *queuedRetrySender) shutdown() { + // First stop the retry goroutines, so that unblocks the queue workers. + close(qrs.retryStopCh) + + // Stop the queued sender, this will drain the queue and will call the retry (which is stopped) that will only + // try once every request. + qrs.queue.Stop() +} + +// TODO: Clean this by forcing all exporters to return an internal error type that always include the information about retries. +type throttleRetry struct { + error + delay time.Duration +} + +func NewThrottleRetry(err error, delay time.Duration) error { + return &throttleRetry{ + error: err, + delay: delay, + } +} + +type retrySender struct { + traceAttribute trace.Attribute + cfg RetrySettings + nextSender requestSender + stopCh chan struct{} + logger *zap.Logger +} + +// send implements the requestSender interface +func (rs *retrySender) send(req request) (int, error) { + if !rs.cfg.Enabled { + n, err := rs.nextSender.send(req) + if err != nil { + rs.logger.Error( + "Exporting failed. Try enabling retry_on_failure config option.", + zap.Error(err), + ) + } + return n, err + } + + // Do not use NewExponentialBackOff since it calls Reset and the code here must + // call Reset after changing the InitialInterval (this saves an unnecessary call to Now). + expBackoff := backoff.ExponentialBackOff{ + InitialInterval: rs.cfg.InitialInterval, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + MaxInterval: rs.cfg.MaxInterval, + MaxElapsedTime: rs.cfg.MaxElapsedTime, + Clock: backoff.SystemClock, + } + expBackoff.Reset() + span := trace.FromContext(req.context()) + retryNum := int64(0) + for { + span.Annotate( + []trace.Attribute{ + rs.traceAttribute, + trace.Int64Attribute("retry_num", retryNum)}, + "Sending request.") + droppedItems, err := rs.nextSender.send(req) + + if err == nil { + return droppedItems, nil + } + + // Immediately drop data on permanent errors. + if consumererror.IsPermanent(err) { + rs.logger.Error( + "Exporting failed. The error is not retryable. Dropping data.", + zap.Error(err), + zap.Int("dropped_items", droppedItems), + ) + return droppedItems, err + } + + // If partial error, update data and stats with non exported data. + if partialErr, isPartial := err.(consumererror.PartialError); isPartial { + req = req.onPartialError(partialErr) + } + + backoffDelay := expBackoff.NextBackOff() + + if backoffDelay == backoff.Stop { + // throw away the batch + err = fmt.Errorf("max elapsed time expired %w", err) + rs.logger.Error( + "Exporting failed. No more retries left. Dropping data.", + zap.Error(err), + zap.Int("dropped_items", droppedItems), + ) + return req.count(), err + } + + if throttleErr, isThrottle := err.(*throttleRetry); isThrottle { + backoffDelay = max(backoffDelay, throttleErr.delay) + } + + backoffDelayStr := backoffDelay.String() + span.Annotate( + []trace.Attribute{ + rs.traceAttribute, + trace.StringAttribute("interval", backoffDelayStr), + trace.StringAttribute("error", err.Error())}, + "Exporting failed. Will retry the request after interval.") + rs.logger.Info( + "Exporting failed. Will retry the request after interval.", + zap.Error(err), + zap.String("interval", backoffDelayStr), + ) + retryNum++ + + // back-off, but get interrupted when shutting down or request is cancelled or timed out. + select { + case <-req.context().Done(): + return req.count(), fmt.Errorf("request is cancelled or timed out %w", err) + case <-rs.stopCh: + return req.count(), fmt.Errorf("interrupted due to shutdown %w", err) + case <-time.After(backoffDelay): + } + } +} + +// max returns the larger of x or y. +func max(x, y time.Duration) time.Duration { + if x < y { + return y + } + return x +} + +type noCancellationContext struct { + context.Context +} + +func (noCancellationContext) Deadline() (deadline time.Time, ok bool) { + return +} + +func (noCancellationContext) Done() <-chan struct{} { + return nil +} + +func (noCancellationContext) Err() error { + return nil +} diff --git a/internal/otel_collector/exporter/exporterhelper/queued_retry_test.go b/internal/otel_collector/exporter/exporterhelper/queued_retry_test.go new file mode 100644 index 00000000000..9df39d34119 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/queued_retry_test.go @@ -0,0 +1,462 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + "errors" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +func TestQueuedRetry_DropOnPermanentError(t *testing.T) { + qCfg := DefaultQueueSettings() + rCfg := DefaultRetrySettings() + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + mockR := newMockRequest(context.Background(), 2, consumererror.Permanent(errors.New("bad data"))) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + // In the newMockConcurrentExporter we count requests and items even for failed requests + mockR.checkNumRequests(t, 1) + ocs.checkSendItemsCount(t, 0) + ocs.checkDroppedItemsCount(t, 2) +} + +func TestQueuedRetry_DropOnNoRetry(t *testing.T) { + qCfg := DefaultQueueSettings() + rCfg := DefaultRetrySettings() + rCfg.Enabled = false + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + mockR := newMockRequest(context.Background(), 2, errors.New("transient error")) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + // In the newMockConcurrentExporter we count requests and items even for failed requests + mockR.checkNumRequests(t, 1) + ocs.checkSendItemsCount(t, 0) + ocs.checkDroppedItemsCount(t, 2) +} + +func TestQueuedRetry_PartialError(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.NumConsumers = 1 + rCfg := DefaultRetrySettings() + rCfg.InitialInterval = 0 + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + partialErr := consumererror.PartialTracesError(errors.New("some error"), testdata.GenerateTraceDataOneSpan()) + mockR := newMockRequest(context.Background(), 2, partialErr) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + + // In the newMockConcurrentExporter we count requests and items even for failed requests + mockR.checkNumRequests(t, 2) + ocs.checkSendItemsCount(t, 2) + ocs.checkDroppedItemsCount(t, 0) +} + +func TestQueuedRetry_StopWhileWaiting(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.NumConsumers = 1 + rCfg := DefaultRetrySettings() + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + + firstMockR := newMockRequest(context.Background(), 2, errors.New("transient error")) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(firstMockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + + // Enqueue another request to ensure when calling shutdown we drain the queue. + secondMockR := newMockRequest(context.Background(), 3, nil) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(secondMockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + + assert.NoError(t, be.Shutdown(context.Background())) + + // TODO: Ensure that queue is drained, and uncomment the next 3 lines. + // https://github.com/jaegertracing/jaeger/pull/2349 + firstMockR.checkNumRequests(t, 1) + // secondMockR.checkNumRequests(t, 1) + // ocs.checkSendItemsCount(t, 3) + ocs.checkDroppedItemsCount(t, 2) + // require.Zero(t, be.qrSender.queue.Size()) +} + +func TestQueuedRetry_DoNotPreserveCancellation(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.NumConsumers = 1 + rCfg := DefaultRetrySettings() + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + ctx, cancelFunc := context.WithCancel(context.Background()) + cancelFunc() + mockR := newMockRequest(ctx, 2, nil) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + + mockR.checkNumRequests(t, 1) + ocs.checkSendItemsCount(t, 2) + ocs.checkDroppedItemsCount(t, 0) + require.Zero(t, be.qrSender.queue.Size()) +} + +func TestQueuedRetry_MaxElapsedTime(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.NumConsumers = 1 + rCfg := DefaultRetrySettings() + rCfg.InitialInterval = time.Millisecond + rCfg.MaxElapsedTime = 100 * time.Millisecond + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + ocs.run(func() { + // Add an item that will always fail. + droppedItems, err := be.sender.send(newErrorRequest(context.Background())) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + + mockR := newMockRequest(context.Background(), 2, nil) + start := time.Now() + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + + // We should ensure that we wait for more than MaxElapsedTime, but not too much. + waitingTime := time.Since(start) + assert.True(t, 100*time.Millisecond < waitingTime) + assert.True(t, 5*time.Second > waitingTime) + + // In the newMockConcurrentExporter we count requests and items even for failed requests. + mockR.checkNumRequests(t, 1) + ocs.checkSendItemsCount(t, 2) + ocs.checkDroppedItemsCount(t, 7) + require.Zero(t, be.qrSender.queue.Size()) +} + +func TestQueuedRetry_ThrottleError(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.NumConsumers = 1 + rCfg := DefaultRetrySettings() + rCfg.InitialInterval = 10 * time.Millisecond + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + mockR := newMockRequest(context.Background(), 2, NewThrottleRetry(errors.New("throttle error"), 100*time.Millisecond)) + start := time.Now() + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + + // The initial backoff is 10ms, but because of the throttle this should wait at least 100ms. + assert.True(t, 100*time.Millisecond < time.Since(start)) + + mockR.checkNumRequests(t, 2) + ocs.checkSendItemsCount(t, 2) + ocs.checkDroppedItemsCount(t, 0) + require.Zero(t, be.qrSender.queue.Size()) +} + +func TestQueuedRetry_RetryOnError(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.NumConsumers = 1 + qCfg.QueueSize = 1 + rCfg := DefaultRetrySettings() + rCfg.InitialInterval = 0 + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + mockR := newMockRequest(context.Background(), 2, errors.New("transient error")) + ocs.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + droppedItems, err := be.sender.send(mockR) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + ocs.awaitAsyncProcessing() + + // In the newMockConcurrentExporter we count requests and items even for failed requests + mockR.checkNumRequests(t, 2) + ocs.checkSendItemsCount(t, 2) + ocs.checkDroppedItemsCount(t, 0) + require.Zero(t, be.qrSender.queue.Size()) +} + +func TestQueuedRetry_DropOnFull(t *testing.T) { + qCfg := DefaultQueueSettings() + qCfg.QueueSize = 0 + rCfg := DefaultRetrySettings() + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + droppedItems, err := be.sender.send(newMockRequest(context.Background(), 2, errors.New("transient error"))) + require.Error(t, err) + assert.Equal(t, 2, droppedItems) +} + +func TestQueuedRetryHappyPath(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + qCfg := DefaultQueueSettings() + rCfg := DefaultRetrySettings() + be := newBaseExporter(defaultExporterCfg, zap.NewNop(), WithRetry(rCfg), WithQueue(qCfg)) + ocs := newObservabilityConsumerSender(be.qrSender.consumerSender) + be.qrSender.consumerSender = ocs + require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, be.Shutdown(context.Background())) + }) + + wantRequests := 10 + reqs := make([]*mockRequest, 0, 10) + for i := 0; i < wantRequests; i++ { + ocs.run(func() { + req := newMockRequest(context.Background(), 2, nil) + reqs = append(reqs, req) + droppedItems, err := be.sender.send(req) + require.NoError(t, err) + assert.Equal(t, 0, droppedItems) + }) + } + + // Wait until all batches received + ocs.awaitAsyncProcessing() + + require.Len(t, reqs, wantRequests) + for _, req := range reqs { + req.checkNumRequests(t, 1) + } + + ocs.checkSendItemsCount(t, 2*wantRequests) + ocs.checkDroppedItemsCount(t, 0) +} + +func TestNoCancellationContext(t *testing.T) { + deadline := time.Now().Add(1 * time.Second) + ctx, cancelFunc := context.WithDeadline(context.Background(), deadline) + cancelFunc() + require.Error(t, ctx.Err()) + d, ok := ctx.Deadline() + require.True(t, ok) + require.Equal(t, deadline, d) + + nctx := noCancellationContext{Context: ctx} + assert.NoError(t, nctx.Err()) + d, ok = nctx.Deadline() + assert.False(t, ok) + assert.True(t, d.IsZero()) +} + +type mockErrorRequest struct { + baseRequest +} + +func (mer *mockErrorRequest) export(_ context.Context) (int, error) { + return 0, errors.New("transient error") +} + +func (mer *mockErrorRequest) onPartialError(consumererror.PartialError) request { + return mer +} + +func (mer *mockErrorRequest) count() int { + return 7 +} + +func newErrorRequest(ctx context.Context) request { + return &mockErrorRequest{ + baseRequest: baseRequest{ctx: ctx}, + } +} + +type mockRequest struct { + baseRequest + cnt int + mu sync.Mutex + consumeError error + requestCount *int64 +} + +func (m *mockRequest) export(ctx context.Context) (int, error) { + atomic.AddInt64(m.requestCount, 1) + m.mu.Lock() + defer m.mu.Unlock() + err := m.consumeError + m.consumeError = nil + if err != nil { + return m.cnt, err + } + // Respond like gRPC/HTTP, if context is cancelled, return error + return 0, ctx.Err() +} + +func (m *mockRequest) onPartialError(consumererror.PartialError) request { + return &mockRequest{ + baseRequest: m.baseRequest, + cnt: 1, + consumeError: nil, + requestCount: m.requestCount, + } +} + +func (m *mockRequest) checkNumRequests(t *testing.T, want int) { + assert.Eventually(t, func() bool { + return int64(want) == atomic.LoadInt64(m.requestCount) + }, time.Second, 1*time.Millisecond) +} + +func (m *mockRequest) count() int { + return m.cnt +} + +func newMockRequest(ctx context.Context, cnt int, consumeError error) *mockRequest { + return &mockRequest{ + baseRequest: baseRequest{ctx: ctx}, + cnt: cnt, + consumeError: consumeError, + requestCount: new(int64), + } +} + +type observabilityConsumerSender struct { + waitGroup *sync.WaitGroup + sentItemsCount int64 + droppedItemsCount int64 + nextSender requestSender +} + +func newObservabilityConsumerSender(nextSender requestSender) *observabilityConsumerSender { + return &observabilityConsumerSender{waitGroup: new(sync.WaitGroup), nextSender: nextSender} +} + +func (ocs *observabilityConsumerSender) send(req request) (int, error) { + dic, err := ocs.nextSender.send(req) + atomic.AddInt64(&ocs.sentItemsCount, int64(req.count()-dic)) + atomic.AddInt64(&ocs.droppedItemsCount, int64(dic)) + ocs.waitGroup.Done() + return dic, err +} + +func (ocs *observabilityConsumerSender) run(fn func()) { + ocs.waitGroup.Add(1) + fn() +} + +func (ocs *observabilityConsumerSender) awaitAsyncProcessing() { + ocs.waitGroup.Wait() +} + +func (ocs *observabilityConsumerSender) checkSendItemsCount(t *testing.T, want int) { + assert.EqualValues(t, want, atomic.LoadInt64(&ocs.sentItemsCount)) +} + +func (ocs *observabilityConsumerSender) checkDroppedItemsCount(t *testing.T, want int) { + assert.EqualValues(t, want, atomic.LoadInt64(&ocs.droppedItemsCount)) +} diff --git a/internal/otel_collector/exporter/exporterhelper/resource_to_label.go b/internal/otel_collector/exporter/exporterhelper/resource_to_label.go new file mode 100644 index 00000000000..53c139e2f7f --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/resource_to_label.go @@ -0,0 +1,116 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +// ResourceToTelemetrySettings defines configuration for converting resource attributes to metric labels. +type ResourceToTelemetrySettings struct { + // Enabled indicates whether to not convert resource attributes to metric labels + Enabled bool `mapstructure:"enabled"` +} + +// defaultResourceToTelemetrySettings returns the default settings for ResourceToTelemetrySettings. +func defaultResourceToTelemetrySettings() ResourceToTelemetrySettings { + return ResourceToTelemetrySettings{ + Enabled: false, + } +} + +// convertResourceToLabels converts all resource attributes to metric labels +func convertResourceToLabels(md pdata.Metrics) pdata.Metrics { + cloneMd := md.Clone() + rms := cloneMd.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + resource := rms.At(i).Resource() + + labelMap := extractLabelsFromResource(&resource) + + ilms := rms.At(i).InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + ilm := ilms.At(j) + metricSlice := ilm.Metrics() + for k := 0; k < metricSlice.Len(); k++ { + metric := metricSlice.At(k) + addLabelsToMetric(&metric, labelMap) + } + } + } + return cloneMd +} + +// extractAttributesFromResource extracts the attributes from a given resource and +// returns them as a StringMap. +func extractLabelsFromResource(resource *pdata.Resource) pdata.StringMap { + labelMap := pdata.NewStringMap() + + attrMap := resource.Attributes() + attrMap.ForEach(func(k string, av pdata.AttributeValue) { + stringLabel := tracetranslator.AttributeValueToString(av, false) + labelMap.Upsert(k, stringLabel) + }) + return labelMap +} + +// addLabelsToMetric adds additional labels to the given metric +func addLabelsToMetric(metric *pdata.Metric, labelMap pdata.StringMap) { + switch metric.DataType() { + case pdata.MetricDataTypeIntGauge: + addLabelsToIntDataPoints(metric.IntGauge().DataPoints(), labelMap) + case pdata.MetricDataTypeDoubleGauge: + addLabelsToDoubleDataPoints(metric.DoubleGauge().DataPoints(), labelMap) + case pdata.MetricDataTypeIntSum: + addLabelsToIntDataPoints(metric.IntSum().DataPoints(), labelMap) + case pdata.MetricDataTypeDoubleSum: + addLabelsToDoubleDataPoints(metric.DoubleSum().DataPoints(), labelMap) + case pdata.MetricDataTypeIntHistogram: + addLabelsToIntHistogramDataPoints(metric.IntHistogram().DataPoints(), labelMap) + case pdata.MetricDataTypeDoubleHistogram: + addLabelsToDoubleHistogramDataPoints(metric.DoubleHistogram().DataPoints(), labelMap) + } +} + +func addLabelsToIntDataPoints(ps pdata.IntDataPointSlice, newLabelMap pdata.StringMap) { + for i := 0; i < ps.Len(); i++ { + joinStringMaps(newLabelMap, ps.At(i).LabelsMap()) + } +} + +func addLabelsToDoubleDataPoints(ps pdata.DoubleDataPointSlice, newLabelMap pdata.StringMap) { + for i := 0; i < ps.Len(); i++ { + joinStringMaps(newLabelMap, ps.At(i).LabelsMap()) + } +} + +func addLabelsToIntHistogramDataPoints(ps pdata.IntHistogramDataPointSlice, newLabelMap pdata.StringMap) { + for i := 0; i < ps.Len(); i++ { + joinStringMaps(newLabelMap, ps.At(i).LabelsMap()) + } +} + +func addLabelsToDoubleHistogramDataPoints(ps pdata.DoubleHistogramDataPointSlice, newLabelMap pdata.StringMap) { + for i := 0; i < ps.Len(); i++ { + joinStringMaps(newLabelMap, ps.At(i).LabelsMap()) + } +} + +func joinStringMaps(from, to pdata.StringMap) { + from.ForEach(func(k, v string) { + to.Upsert(k, v) + }) +} diff --git a/internal/otel_collector/exporter/exporterhelper/resource_to_label_test.go b/internal/otel_collector/exporter/exporterhelper/resource_to_label_test.go new file mode 100644 index 00000000000..88facb1134b --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/resource_to_label_test.go @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package exporterhelper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestConvertResourceToLabels(t *testing.T) { + md := testdata.GenerateMetricsOneMetric() + assert.NotNil(t, md) + + // Before converting resource to labels + assert.Equal(t, 1, md.ResourceMetrics().At(0).Resource().Attributes().Len()) + assert.Equal(t, 1, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).IntSum().DataPoints().At(0).LabelsMap().Len()) + + cloneMd := convertResourceToLabels(md) + + // After converting resource to labels + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).Resource().Attributes().Len()) + assert.Equal(t, 2, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).IntSum().DataPoints().At(0).LabelsMap().Len()) + + assert.Equal(t, 1, md.ResourceMetrics().At(0).Resource().Attributes().Len()) + assert.Equal(t, 1, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).IntSum().DataPoints().At(0).LabelsMap().Len()) + +} + +func TestConvertResourceToLabelsAllDataTypesEmptyDataPoint(t *testing.T) { + md := testdata.GenerateMetricsAllTypesEmptyDataPoint() + assert.NotNil(t, md) + + // Before converting resource to labels + assert.Equal(t, 1, md.ResourceMetrics().At(0).Resource().Attributes().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).DoubleGauge().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(1).IntGauge().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(2).DoubleSum().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(3).IntSum().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(4).DoubleHistogram().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(5).IntHistogram().DataPoints().At(0).LabelsMap().Len()) + + cloneMd := convertResourceToLabels(md) + + // After converting resource to labels + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).Resource().Attributes().Len()) + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).DoubleGauge().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(1).IntGauge().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(2).DoubleSum().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(3).IntSum().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(4).DoubleHistogram().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 1, cloneMd.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(5).IntHistogram().DataPoints().At(0).LabelsMap().Len()) + + assert.Equal(t, 1, md.ResourceMetrics().At(0).Resource().Attributes().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).DoubleGauge().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(1).IntGauge().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(2).DoubleSum().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(3).IntSum().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(4).DoubleHistogram().DataPoints().At(0).LabelsMap().Len()) + assert.Equal(t, 0, md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(5).IntHistogram().DataPoints().At(0).LabelsMap().Len()) + +} diff --git a/internal/otel_collector/exporter/exporterhelper/tracehelper.go b/internal/otel_collector/exporter/exporterhelper/tracehelper.go new file mode 100644 index 00000000000..9e6f30a3334 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/tracehelper.go @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporterhelper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" +) + +// PushTraces is a helper function that is similar to ConsumeTraces but also +// returns the number of dropped spans. +type PushTraces func(ctx context.Context, td pdata.Traces) (droppedSpans int, err error) + +type tracesRequest struct { + baseRequest + td pdata.Traces + pusher PushTraces +} + +func newTracesRequest(ctx context.Context, td pdata.Traces, pusher PushTraces) request { + return &tracesRequest{ + baseRequest: baseRequest{ctx: ctx}, + td: td, + pusher: pusher, + } +} + +func (req *tracesRequest) onPartialError(partialErr consumererror.PartialError) request { + return newTracesRequest(req.ctx, partialErr.GetTraces(), req.pusher) +} + +func (req *tracesRequest) export(ctx context.Context) (int, error) { + return req.pusher(ctx, req.td) +} + +func (req *tracesRequest) count() int { + return req.td.SpanCount() +} + +type traceExporter struct { + *baseExporter + pusher PushTraces +} + +func (texp *traceExporter) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + exporterCtx := obsreport.ExporterContext(ctx, texp.cfg.Name()) + req := newTracesRequest(exporterCtx, td, texp.pusher) + _, err := texp.sender.send(req) + return err +} + +// NewTraceExporter creates a TracesExporter that records observability metrics and wraps every request with a Span. +func NewTraceExporter( + cfg configmodels.Exporter, + logger *zap.Logger, + pusher PushTraces, + options ...Option, +) (component.TracesExporter, error) { + + if cfg == nil { + return nil, errNilConfig + } + + if logger == nil { + return nil, errNilLogger + } + + if pusher == nil { + return nil, errNilPushTraceData + } + + be := newBaseExporter(cfg, logger, options...) + be.wrapConsumerSender(func(nextSender requestSender) requestSender { + return &tracesExporterWithObservability{ + obsrep: obsreport.NewExporterObsReport(configtelemetry.GetMetricsLevelFlagValue(), cfg.Name()), + nextSender: nextSender, + } + }) + + return &traceExporter{ + baseExporter: be, + pusher: pusher, + }, nil +} + +type tracesExporterWithObservability struct { + obsrep *obsreport.ExporterObsReport + nextSender requestSender +} + +func (tewo *tracesExporterWithObservability) send(req request) (int, error) { + req.setContext(tewo.obsrep.StartTracesExportOp(req.context())) + // Forward the data to the next consumer (this pusher is the next). + droppedSpans, err := tewo.nextSender.send(req) + + // TODO: this is not ideal: it should come from the next function itself. + // temporarily loading it from internal format. Once full switch is done + // to new metrics will remove this. + tewo.obsrep.EndTracesExportOp(req.context(), req.count(), err) + return droppedSpans, err +} diff --git a/internal/otel_collector/exporter/exporterhelper/tracehelper_test.go b/internal/otel_collector/exporter/exporterhelper/tracehelper_test.go new file mode 100644 index 00000000000..057c0e03d30 --- /dev/null +++ b/internal/otel_collector/exporter/exporterhelper/tracehelper_test.go @@ -0,0 +1,252 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package exporterhelper + +import ( + "context" + "errors" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/trace" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +const ( + fakeTraceExporterType = "fake_trace_exporter" + fakeTraceExporterName = "fake_trace_exporter/with_name" + fakeTraceParentSpanName = "fake_trace_parent_span_name" +) + +var ( + fakeTraceExporterConfig = &configmodels.ExporterSettings{ + TypeVal: fakeTraceExporterType, + NameVal: fakeTraceExporterName, + } +) + +func TestTracesRequest(t *testing.T) { + mr := newTracesRequest(context.Background(), testdata.GenerateTraceDataOneSpan(), nil) + + partialErr := consumererror.PartialTracesError(errors.New("some error"), testdata.GenerateTraceDataEmpty()) + assert.EqualValues(t, newTracesRequest(context.Background(), testdata.GenerateTraceDataEmpty(), nil), mr.onPartialError(partialErr.(consumererror.PartialError))) +} + +type testOCTraceExporter struct { + mu sync.Mutex + spanData []*trace.SpanData +} + +func (tote *testOCTraceExporter) ExportSpan(sd *trace.SpanData) { + tote.mu.Lock() + defer tote.mu.Unlock() + + tote.spanData = append(tote.spanData, sd) +} + +func TestTraceExporter_InvalidName(t *testing.T) { + te, err := NewTraceExporter(nil, zap.NewNop(), newTraceDataPusher(0, nil)) + require.Nil(t, te) + require.Equal(t, errNilConfig, err) +} + +func TestTraceExporter_NilLogger(t *testing.T) { + te, err := NewTraceExporter(fakeTraceExporterConfig, nil, newTraceDataPusher(0, nil)) + require.Nil(t, te) + require.Equal(t, errNilLogger, err) +} + +func TestTraceExporter_NilPushTraceData(t *testing.T) { + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), nil) + require.Nil(t, te) + require.Equal(t, errNilPushTraceData, err) +} + +func TestTraceExporter_Default(t *testing.T) { + td := pdata.NewTraces() + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, nil)) + assert.NotNil(t, te) + assert.NoError(t, err) + + assert.Nil(t, te.ConsumeTraces(context.Background(), td)) + assert.Nil(t, te.Shutdown(context.Background())) +} + +func TestTraceExporter_Default_ReturnError(t *testing.T) { + td := pdata.NewTraces() + want := errors.New("my_error") + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, want)) + require.Nil(t, err) + require.NotNil(t, te) + + err = te.ConsumeTraces(context.Background(), td) + require.Equalf(t, want, err, "ConsumeTraceData returns: Want %v Got %v", want, err) +} + +func TestTraceExporter_WithRecordMetrics(t *testing.T) { + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, nil)) + require.Nil(t, err) + require.NotNil(t, te) + + checkRecordedMetricsForTraceExporter(t, te, nil) +} + +func TestTraceExporter_WithRecordMetrics_NonZeroDropped(t *testing.T) { + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(1, nil)) + require.Nil(t, err) + require.NotNil(t, te) + + checkRecordedMetricsForTraceExporter(t, te, nil) +} + +func TestTraceExporter_WithRecordMetrics_ReturnError(t *testing.T) { + want := errors.New("my_error") + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, want)) + require.Nil(t, err) + require.NotNil(t, te) + + checkRecordedMetricsForTraceExporter(t, te, want) +} + +func TestTraceExporter_WithSpan(t *testing.T) { + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, nil)) + require.Nil(t, err) + require.NotNil(t, te) + + checkWrapSpanForTraceExporter(t, te, nil, 1) +} + +func TestTraceExporter_WithSpan_NonZeroDropped(t *testing.T) { + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(1, nil)) + require.Nil(t, err) + require.NotNil(t, te) + + checkWrapSpanForTraceExporter(t, te, nil, 1) +} + +func TestTraceExporter_WithSpan_ReturnError(t *testing.T) { + want := errors.New("my_error") + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, want)) + require.Nil(t, err) + require.NotNil(t, te) + + checkWrapSpanForTraceExporter(t, te, want, 1) +} + +func TestTraceExporter_WithShutdown(t *testing.T) { + shutdownCalled := false + shutdown := func(context.Context) error { shutdownCalled = true; return nil } + + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, nil), WithShutdown(shutdown)) + assert.NotNil(t, te) + assert.NoError(t, err) + + assert.Nil(t, te.Shutdown(context.Background())) + assert.True(t, shutdownCalled) +} + +func TestTraceExporter_WithShutdown_ReturnError(t *testing.T) { + want := errors.New("my_error") + shutdownErr := func(context.Context) error { return want } + + te, err := NewTraceExporter(fakeTraceExporterConfig, zap.NewNop(), newTraceDataPusher(0, nil), WithShutdown(shutdownErr)) + assert.NotNil(t, te) + assert.NoError(t, err) + + assert.Equal(t, te.Shutdown(context.Background()), want) +} + +func newTraceDataPusher(droppedSpans int, retError error) PushTraces { + return func(ctx context.Context, td pdata.Traces) (int, error) { + return droppedSpans, retError + } +} + +func checkRecordedMetricsForTraceExporter(t *testing.T, te component.TracesExporter, wantError error) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + td := testdata.GenerateTraceDataTwoSpansSameResource() + const numBatches = 7 + for i := 0; i < numBatches; i++ { + require.Equal(t, wantError, te.ConsumeTraces(context.Background(), td)) + } + + // TODO: When the new metrics correctly count partial dropped fix this. + if wantError != nil { + obsreporttest.CheckExporterTracesViews(t, fakeTraceExporterName, 0, int64(numBatches*td.SpanCount())) + } else { + obsreporttest.CheckExporterTracesViews(t, fakeTraceExporterName, int64(numBatches*td.SpanCount()), 0) + } +} + +func generateTraceTraffic(t *testing.T, te component.TracesExporter, numRequests int, wantError error) { + td := pdata.NewTraces() + rs := td.ResourceSpans() + rs.Resize(1) + rs.At(0).InstrumentationLibrarySpans().Resize(1) + rs.At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + ctx, span := trace.StartSpan(context.Background(), fakeTraceParentSpanName, trace.WithSampler(trace.AlwaysSample())) + defer span.End() + for i := 0; i < numRequests; i++ { + require.Equal(t, wantError, te.ConsumeTraces(ctx, td)) + } +} + +func checkWrapSpanForTraceExporter(t *testing.T, te component.TracesExporter, wantError error, numSpans int64) { + ocSpansSaver := new(testOCTraceExporter) + trace.RegisterExporter(ocSpansSaver) + defer trace.UnregisterExporter(ocSpansSaver) + + const numRequests = 5 + generateTraceTraffic(t, te, numRequests, wantError) + + // Inspection time! + ocSpansSaver.mu.Lock() + defer ocSpansSaver.mu.Unlock() + + require.NotEqual(t, 0, len(ocSpansSaver.spanData), "No exported span data.") + + gotSpanData := ocSpansSaver.spanData + require.Equal(t, numRequests+1, len(gotSpanData)) + + parentSpan := gotSpanData[numRequests] + require.Equalf(t, fakeTraceParentSpanName, parentSpan.Name, "SpanData %v", parentSpan) + + for _, sd := range gotSpanData[:numRequests] { + require.Equalf(t, parentSpan.SpanContext.SpanID, sd.ParentSpanID, "Exporter span not a child\nSpanData %v", sd) + require.Equalf(t, errToStatus(wantError), sd.Status, "SpanData %v", sd) + + sentSpans := numSpans + var failedToSendSpans int64 + if wantError != nil { + sentSpans = 0 + failedToSendSpans = numSpans + } + + require.Equalf(t, sentSpans, sd.Attributes[obsreport.SentSpansKey], "SpanData %v", sd) + require.Equalf(t, failedToSendSpans, sd.Attributes[obsreport.FailedToSendSpansKey], "SpanData %v", sd) + } +} diff --git a/internal/otel_collector/exporter/fileexporter/README.md b/internal/otel_collector/exporter/fileexporter/README.md new file mode 100644 index 00000000000..0ae26b4d128 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/README.md @@ -0,0 +1,26 @@ +# File Exporter + +This exporter will write pipeline data to a JSON file. The data is written in +[Protobuf JSON +encoding](https://developers.google.com/protocol-buffers/docs/proto3#json) +using [OpenTelemetry +protocol](https://github.com/open-telemetry/opentelemetry-proto). + +Please note that there is no guarantee that exact field names will remain stable. +This intended for primarily for debugging Collector without setting up backends. + +Supported pipeline types: traces, metrics, logs + +## Getting Started + +The following settings are required: + +- `path` (no default): where to write information. + +Example: + +```yaml +exporters: + file: + path: ./filename.json +``` diff --git a/internal/otel_collector/exporter/fileexporter/config.go b/internal/otel_collector/exporter/fileexporter/config.go new file mode 100644 index 00000000000..e1086657960 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/config.go @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileexporter + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for file exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + + // Path of the file to write to. Path is relative to current directory. + Path string `mapstructure:"path"` +} diff --git a/internal/otel_collector/exporter/fileexporter/config_test.go b/internal/otel_collector/exporter/fileexporter/config_test.go new file mode 100644 index 00000000000..89d42d0aab6 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/config_test.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileexporter + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["file"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["file/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "file/2", + TypeVal: "file", + }, + Path: "./filename.json", + }) +} diff --git a/internal/otel_collector/exporter/fileexporter/factory.go b/internal/otel_collector/exporter/fileexporter/factory.go new file mode 100644 index 00000000000..4f72ccd6ee3 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/factory.go @@ -0,0 +1,100 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileexporter + +import ( + "context" + "os" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "file" +) + +// NewFactory creates a factory for OTLP exporter. +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter), + exporterhelper.WithMetrics(createMetricsExporter), + exporterhelper.WithLogs(createLogsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createTraceExporter( + _ context.Context, + _ component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.TracesExporter, error) { + return createExporter(cfg) +} + +func createMetricsExporter( + _ context.Context, + _ component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.MetricsExporter, error) { + return createExporter(cfg) +} + +func createLogsExporter( + _ context.Context, + _ component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.LogsExporter, error) { + return createExporter(cfg) +} + +func createExporter(config configmodels.Exporter) (*fileExporter, error) { + cfg := config.(*Config) + + // There must be one exporter for metrics, traces, and logs. We maintain a + // map of exporters per config. + + // Check to see if there is already a exporter for this config. + exporter, ok := exporters[cfg] + + if !ok { + file, err := os.OpenFile(cfg.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return nil, err + } + exporter = &fileExporter{file: file} + + // Remember the receiver in the map + exporters[cfg] = exporter + } + return exporter, nil +} + +// This is the map of already created File exporters for particular configurations. +// We maintain this map because the Factory is asked trace and metric receivers separately +// when it gets CreateTracesReceiver() and CreateMetricsReceiver() but they must not +// create separate objects, they must use one Receiver object per configuration. +var exporters = map[*Config]*fileExporter{} diff --git a/internal/otel_collector/exporter/fileexporter/factory_test.go b/internal/otel_collector/exporter/fileexporter/factory_test.go new file mode 100644 index 00000000000..a0e08a84418 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/factory_test.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateMetricsExporter(t *testing.T) { + cfg := createDefaultConfig() + exp, err := createMetricsExporter( + context.Background(), + component.ExporterCreateParams{Logger: zap.NewNop()}, + cfg) + assert.Error(t, err) + require.Nil(t, exp) +} + +func TestCreateTraceExporter(t *testing.T) { + cfg := createDefaultConfig() + exp, err := createTraceExporter( + context.Background(), + component.ExporterCreateParams{Logger: zap.NewNop()}, + cfg) + assert.Error(t, err) + require.Nil(t, exp) +} + +func TestCreateLogsExporter(t *testing.T) { + cfg := createDefaultConfig() + + exp, err := createLogsExporter( + context.Background(), + component.ExporterCreateParams{Logger: zap.NewNop()}, + cfg) + assert.Error(t, err) + require.Nil(t, exp) +} diff --git a/internal/otel_collector/exporter/fileexporter/file_exporter.go b/internal/otel_collector/exporter/fileexporter/file_exporter.go new file mode 100644 index 00000000000..a6edfc0d2c1 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/file_exporter.go @@ -0,0 +1,84 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileexporter + +import ( + "context" + "io" + "sync" + + "github.com/gogo/protobuf/jsonpb" + "github.com/gogo/protobuf/proto" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" +) + +// Marshaler configuration used for marhsaling Protobuf to JSON. Use default config. +var marshaler = &jsonpb.Marshaler{} + +// fileExporter is the implementation of file exporter that writes telemetry data to a file +// in Protobuf-JSON format. +type fileExporter struct { + file io.WriteCloser + mutex sync.Mutex +} + +func (e *fileExporter) ConsumeTraces(_ context.Context, td pdata.Traces) error { + request := otlptrace.ExportTraceServiceRequest{ + ResourceSpans: pdata.TracesToOtlp(td), + } + return exportMessageAsLine(e, &request) +} + +func (e *fileExporter) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + request := otlpmetrics.ExportMetricsServiceRequest{ + ResourceMetrics: pdata.MetricsToOtlp(md), + } + return exportMessageAsLine(e, &request) +} + +func (e *fileExporter) ConsumeLogs(_ context.Context, ld pdata.Logs) error { + request := otlplogs.ExportLogsServiceRequest{ + ResourceLogs: internal.LogsToOtlp(ld.InternalRep()), + } + return exportMessageAsLine(e, &request) +} + +func exportMessageAsLine(e *fileExporter, message proto.Message) error { + // Ensure only one write operation happens at a time. + e.mutex.Lock() + defer e.mutex.Unlock() + if err := marshaler.Marshal(e.file, message); err != nil { + return err + } + if _, err := io.WriteString(e.file, "\n"); err != nil { + return err + } + return nil +} + +func (e *fileExporter) Start(ctx context.Context, host component.Host) error { + return nil +} + +// Shutdown stops the exporter and is invoked during shutdown. +func (e *fileExporter) Shutdown(context.Context) error { + return e.file.Close() +} diff --git a/internal/otel_collector/exporter/fileexporter/file_exporter_test.go b/internal/otel_collector/exporter/fileexporter/file_exporter_test.go new file mode 100644 index 00000000000..a5093147613 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/file_exporter_test.go @@ -0,0 +1,217 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package fileexporter + +import ( + "context" + "testing" + "time" + + "github.com/gogo/protobuf/jsonpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal" + collectorlogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + collectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + logspb "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" + otresourcepb "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/testutil" +) + +func TestFileTraceExporterNoErrors(t *testing.T) { + mf := &testutil.LimitedWriter{} + lte := &fileExporter{file: mf} + require.NotNil(t, lte) + + td := testdata.GenerateTraceDataTwoSpansSameResource() + + assert.NoError(t, lte.ConsumeTraces(context.Background(), td)) + assert.NoError(t, lte.Shutdown(context.Background())) + + var unmarshaler = &jsonpb.Unmarshaler{} + var j collectortrace.ExportTraceServiceRequest + assert.NoError(t, unmarshaler.Unmarshal(mf, &j)) + + assert.EqualValues(t, pdata.TracesToOtlp(td), j.ResourceSpans) +} + +func TestFileMetricsExporterNoErrors(t *testing.T) { + mf := &testutil.LimitedWriter{} + lme := &fileExporter{file: mf} + require.NotNil(t, lme) + + md := testdata.GenerateMetricsTwoMetrics() + assert.NoError(t, lme.ConsumeMetrics(context.Background(), md)) + assert.NoError(t, lme.Shutdown(context.Background())) + + var unmarshaler = &jsonpb.Unmarshaler{} + var j collectormetrics.ExportMetricsServiceRequest + assert.NoError(t, unmarshaler.Unmarshal(mf, &j)) + + assert.EqualValues(t, pdata.MetricsToOtlp(md), j.ResourceMetrics) +} + +func TestFileLogsExporterNoErrors(t *testing.T) { + mf := &testutil.LimitedWriter{} + exporter := &fileExporter{file: mf} + require.NotNil(t, exporter) + + now := time.Now() + ld := []*logspb.ResourceLogs{ + { + Resource: otresourcepb.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "attr1", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "value1"}}, + }, + }, + }, + InstrumentationLibraryLogs: []*logspb.InstrumentationLibraryLogs{ + { + Logs: []*logspb.LogRecord{ + { + TimeUnixNano: uint64(now.UnixNano()), + Name: "logA", + }, + { + TimeUnixNano: uint64(now.UnixNano()), + Name: "logB", + }, + }, + }, + }, + }, + { + Resource: otresourcepb.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "attr2", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "value2"}}, + }, + }, + }, + InstrumentationLibraryLogs: []*logspb.InstrumentationLibraryLogs{ + { + Logs: []*logspb.LogRecord{ + { + TimeUnixNano: uint64(now.UnixNano()), + Name: "logC", + }, + }, + }, + }, + }, + } + assert.NoError(t, exporter.ConsumeLogs(context.Background(), pdata.LogsFromInternalRep(internal.LogsFromOtlp(ld)))) + assert.NoError(t, exporter.Shutdown(context.Background())) + + var unmarshaler = &jsonpb.Unmarshaler{} + var j collectorlogs.ExportLogsServiceRequest + + assert.NoError(t, unmarshaler.Unmarshal(mf, &j)) + assert.EqualValues(t, ld, j.ResourceLogs) +} + +func TestFileLogsExporterErrors(t *testing.T) { + + now := time.Now() + ld := []*logspb.ResourceLogs{ + { + Resource: otresourcepb.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "attr1", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "value1"}}, + }, + }, + }, + InstrumentationLibraryLogs: []*logspb.InstrumentationLibraryLogs{ + { + Logs: []*logspb.LogRecord{ + { + TimeUnixNano: uint64(now.UnixNano()), + Name: "logA", + }, + { + TimeUnixNano: uint64(now.UnixNano()), + Name: "logB", + }, + }, + }, + }, + }, + { + Resource: otresourcepb.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "attr2", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "value2"}}, + }, + }, + }, + InstrumentationLibraryLogs: []*logspb.InstrumentationLibraryLogs{ + { + Logs: []*logspb.LogRecord{ + { + TimeUnixNano: uint64(now.UnixNano()), + Name: "logC", + }, + }, + }, + }, + }, + } + + cases := []struct { + Name string + MaxLen int + }{ + { + Name: "opening", + MaxLen: 1, + }, + { + Name: "resource", + MaxLen: 16, + }, + { + Name: "log_start", + MaxLen: 78, + }, + { + Name: "logs", + MaxLen: 128, + }, + } + + for i := range cases { + maxLen := cases[i].MaxLen + t.Run(cases[i].Name, func(t *testing.T) { + mf := &testutil.LimitedWriter{ + MaxLen: maxLen, + } + exporter := &fileExporter{file: mf} + require.NotNil(t, exporter) + + assert.Error(t, exporter.ConsumeLogs(context.Background(), pdata.LogsFromInternalRep(internal.LogsFromOtlp(ld)))) + assert.NoError(t, exporter.Shutdown(context.Background())) + }) + } +} diff --git a/internal/otel_collector/exporter/fileexporter/testdata/config.yaml b/internal/otel_collector/exporter/fileexporter/testdata/config.yaml new file mode 100644 index 00000000000..f3d046a1692 --- /dev/null +++ b/internal/otel_collector/exporter/fileexporter/testdata/config.yaml @@ -0,0 +1,26 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + file: + file/2: + # This will write the pipeline data to a JSON file. + # The data is written in Protobuf JSON encoding + # (https://developers.google.com/protocol-buffers/docs/proto3#json). + # Note that there are no compatibility guarantees for this format, since it + # just a dump of internal structures which can be changed over time. + # This intended for primarily for debugging Collector without setting up backends. + path: ./filename.json + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [file] + metrics: + receivers: [examplereceiver] + exporters: [file,file/2] diff --git a/internal/otel_collector/exporter/jaegerexporter/README.md b/internal/otel_collector/exporter/jaegerexporter/README.md new file mode 100644 index 00000000000..1cca7c0e285 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/README.md @@ -0,0 +1,49 @@ +# Jaeger gRPC Exporter + +Exports data via gRPC to [Jaeger](https://www.jaegertracing.io/) destinations. +By default, this exporter requires TLS and offers queued retry capabilities. A +Jaeger Thrift HTTP exporter is available in the [contrib +repository](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/master/exporter/jaegerthrifthttpexporter). + +Supported pipeline types: traces + +## Getting Started + +The following settings are required: + +- `endpoint` (no default): host:port to which the exporter is going to send Jaeger trace data, +using the gRPC protocol. The valid syntax is described +[here](https://github.com/grpc/grpc/blob/master/doc/naming.md) + +By default, TLS is enabled: + +- `insecure` (default = `false`): whether to enable client transport security for + the exporter's connection. + +As a result, the following parameters are also required: + +- `cert_file` (no default): path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to false. +- `key_file` (no default): path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to false. + +Example: + +```yaml +exporters: + jaeger: + endpoint: jaeger-all-in-one:14250 + cert_file: file.cert + key_file: file.key + jaeger/2: + endpoint: jaeger-all-in-one:14250 + insecure: true +``` + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) diff --git a/internal/otel_collector/exporter/jaegerexporter/config.go b/internal/otel_collector/exporter/jaegerexporter/config.go new file mode 100644 index 00000000000..d0bf6fb1e32 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/config.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerexporter + +import ( + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// Config defines configuration for Jaeger gRPC exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.TimeoutSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + configgrpc.GRPCClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. +} diff --git a/internal/otel_collector/exporter/jaegerexporter/config_test.go b/internal/otel_collector/exporter/jaegerexporter/config_test.go new file mode 100644 index 00000000000..4aee77bdcfa --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/config_test.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerexporter + +import ( + "context" + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["jaeger"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["jaeger/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "jaeger/2", + TypeVal: "jaeger", + }, + TimeoutSettings: exporterhelper.TimeoutSettings{ + Timeout: 10 * time.Second, + }, + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 10 * time.Second, + MaxInterval: 1 * time.Minute, + MaxElapsedTime: 10 * time.Minute, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 2, + QueueSize: 10, + }, + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: "a.new.target:1234", + WriteBufferSize: 512 * 1024, + BalancerName: "round_robin", + }, + }) + + params := component.ExporterCreateParams{Logger: zap.NewNop()} + te, err := factory.CreateTracesExporter(context.Background(), params, e1) + require.NoError(t, err) + require.NotNil(t, te) +} diff --git a/internal/otel_collector/exporter/jaegerexporter/doc.go b/internal/otel_collector/exporter/jaegerexporter/doc.go new file mode 100644 index 00000000000..e66a34822ad --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package jaegerexporter implements an exporter that sends trace data to +// a Jaeger collector gRPC endpoint. +package jaegerexporter diff --git a/internal/otel_collector/exporter/jaegerexporter/exporter.go b/internal/otel_collector/exporter/jaegerexporter/exporter.go new file mode 100644 index 00000000000..188d4d3d138 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/exporter.go @@ -0,0 +1,102 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerexporter + +import ( + "context" + "fmt" + + jaegerproto "github.com/jaegertracing/jaeger/proto-gen/api_v2" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" + jaegertranslator "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +// newTraceExporter returns a new Jaeger gRPC exporter. +// The exporter name is the name to be used in the observability of the exporter. +// The collectorEndpoint should be of the form "hostname:14250" (a gRPC target). +func newTraceExporter(cfg *Config, logger *zap.Logger) (component.TracesExporter, error) { + + opts, err := cfg.GRPCClientSettings.ToDialOptions() + if err != nil { + return nil, err + } + + client, err := grpc.Dial(cfg.GRPCClientSettings.Endpoint, opts...) + if err != nil { + return nil, err + } + + collectorServiceClient := jaegerproto.NewCollectorServiceClient(client) + s := &protoGRPCSender{ + logger: logger, + client: collectorServiceClient, + metadata: metadata.New(cfg.GRPCClientSettings.Headers), + waitForReady: cfg.WaitForReady, + } + + exp, err := exporterhelper.NewTraceExporter( + cfg, logger, s.pushTraceData, + exporterhelper.WithTimeout(cfg.TimeoutSettings), + exporterhelper.WithRetry(cfg.RetrySettings), + exporterhelper.WithQueue(cfg.QueueSettings), + ) + + return exp, err +} + +// protoGRPCSender forwards spans encoded in the jaeger proto +// format, to a grpc server. +type protoGRPCSender struct { + logger *zap.Logger + client jaegerproto.CollectorServiceClient + metadata metadata.MD + waitForReady bool +} + +func (s *protoGRPCSender) pushTraceData( + ctx context.Context, + td pdata.Traces, +) (droppedSpans int, err error) { + + batches, err := jaegertranslator.InternalTracesToJaegerProto(td) + if err != nil { + return td.SpanCount(), consumererror.Permanent(fmt.Errorf("failed to push trace data via Jaeger exporter: %w", err)) + } + + if s.metadata.Len() > 0 { + ctx = metadata.NewOutgoingContext(ctx, s.metadata) + } + + var sentSpans int + for _, batch := range batches { + _, err = s.client.PostSpans( + ctx, + &jaegerproto.PostSpansRequest{Batch: *batch}, grpc.WaitForReady(s.waitForReady)) + if err != nil { + s.logger.Debug("failed to push trace data to Jaeger", zap.Error(err)) + return td.SpanCount() - sentSpans, fmt.Errorf("failed to push trace data via Jaeger exporter: %w", err) + } + sentSpans += len(batch.Spans) + } + + return 0, nil +} diff --git a/internal/otel_collector/exporter/jaegerexporter/exporter_test.go b/internal/otel_collector/exporter/jaegerexporter/exporter_test.go new file mode 100644 index 00000000000..ff2fcc04ef2 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/exporter_test.go @@ -0,0 +1,278 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerexporter + +import ( + "context" + "net" + "path" + "sync" + "testing" + + "github.com/jaegertracing/jaeger/model" + "github.com/jaegertracing/jaeger/proto-gen/api_v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/data" + tracev1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestNew(t *testing.T) { + type args struct { + config Config + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "createExporter", + args: args{ + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: nil, + Endpoint: "foo.bar", + Compression: "", + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + Keepalive: nil, + }, + }, + }, + }, + { + name: "createExporterWithHeaders", + args: args{ + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: map[string]string{"extra-header": "header-value"}, + Endpoint: "foo.bar", + Compression: "", + Keepalive: nil, + }, + }, + }, + }, + { + name: "createBasicSecureExporter", + args: args{ + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: nil, + Endpoint: "foo.bar", + Compression: "", + Keepalive: nil, + }, + }, + }, + }, + { + name: "createSecureExporterWithClientTLS", + args: args{ + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: nil, + Endpoint: "foo.bar", + Compression: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "testdata/test_cert.pem", + }, + Insecure: false, + }, + Keepalive: nil, + }, + }, + }, + }, + { + name: "createSecureExporterWithKeepAlive", + args: args{ + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: nil, + Endpoint: "foo.bar", + Compression: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "testdata/test_cert.pem", + }, + Insecure: false, + ServerName: "", + }, + Keepalive: &configgrpc.KeepaliveClientConfig{ + Time: 0, + Timeout: 0, + PermitWithoutStream: false, + }, + }, + }, + }, + }, + { + name: "createSecureExporterWithMissingFile", + args: args{ + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: nil, + Endpoint: "foo.bar", + Compression: "", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "testdata/test_cert_missing.pem", + }, + Insecure: false, + }, + Keepalive: nil, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newTraceExporter(&tt.args.config, zap.NewNop()) + if (err != nil) != tt.wantErr { + t.Errorf("newTraceExporter() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got == nil { + return + } + + // This is expected to fail. + err = got.ConsumeTraces(context.Background(), testdata.GenerateTraceDataNoLibraries()) + assert.Error(t, err) + }) + } +} + +// CA key and cert +// openssl req -new -nodes -x509 -days 9650 -keyout ca.key -out ca.crt -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost" +// Server key and cert +// openssl genrsa -des3 -out server.key 1024 +// openssl req -new -key server.key -out server.csr -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost" +// openssl x509 -req -days 9650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt +// Client key and cert +// openssl genrsa -des3 -out client.key 1024 +// openssl req -new -key client.key -out client.csr -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost" +// openssl x509 -req -days 9650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt +// Remove passphrase +// openssl rsa -in server.key -out temp.key && rm server.key && mv temp.key server.key +// openssl rsa -in client.key -out temp.key && rm client.key && mv temp.key client.key +func TestMutualTLS(t *testing.T) { + caPath := path.Join(".", "testdata", "ca.crt") + serverCertPath := path.Join(".", "testdata", "server.crt") + serverKeyPath := path.Join(".", "testdata", "server.key") + clientCertPath := path.Join(".", "testdata", "client.crt") + clientKeyPath := path.Join(".", "testdata", "client.key") + + // start gRPC Jaeger server + tlsCfgOpts := configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: serverCertPath, + KeyFile: serverKeyPath, + }, + ClientCAFile: caPath, + } + tlsCfg, err := tlsCfgOpts.LoadTLSConfig() + require.NoError(t, err) + spanHandler := &mockSpanHandler{} + server, serverAddr := initializeGRPCTestServer(t, func(server *grpc.Server) { + api_v2.RegisterCollectorServiceServer(server, spanHandler) + }, grpc.Creds(credentials.NewTLS(tlsCfg))) + defer server.GracefulStop() + + // Create gRPC trace exporter + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + // Disable queuing to ensure that we execute the request when calling ConsumeTraces + // otherwise we will have to wait. + cfg.QueueSettings.Enabled = false + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: serverAddr.String(), + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: caPath, + CertFile: clientCertPath, + KeyFile: clientKeyPath, + }, + Insecure: false, + ServerName: "localhost", + }, + } + exporter, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + err = exporter.Start(context.Background(), nil) + require.NoError(t, err) + defer exporter.Shutdown(context.Background()) + + traceID := data.NewTraceID([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + spanID := data.NewSpanID([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + traces := pdata.TracesFromOtlp([]*tracev1.ResourceSpans{ + {InstrumentationLibrarySpans: []*tracev1.InstrumentationLibrarySpans{{Spans: []*tracev1.Span{{TraceId: traceID, SpanId: spanID}}}}}, + }) + err = exporter.ConsumeTraces(context.Background(), traces) + require.NoError(t, err) + requestes := spanHandler.getRequests() + assert.Equal(t, 1, len(requestes)) + tidBytes := traceID.Bytes() + jTraceID, err := model.TraceIDFromBytes(tidBytes[:]) + require.NoError(t, err) + require.Len(t, requestes, 1) + require.Len(t, requestes[0].GetBatch().Spans, 1) + assert.Equal(t, jTraceID, requestes[0].GetBatch().Spans[0].TraceID) +} + +func initializeGRPCTestServer(t *testing.T, beforeServe func(server *grpc.Server), opts ...grpc.ServerOption) (*grpc.Server, net.Addr) { + server := grpc.NewServer(opts...) + lis, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + beforeServe(server) + go func() { + require.NoError(t, server.Serve(lis)) + }() + return server, lis.Addr() +} + +type mockSpanHandler struct { + mux sync.Mutex + requests []*api_v2.PostSpansRequest +} + +func (h *mockSpanHandler) getRequests() []*api_v2.PostSpansRequest { + h.mux.Lock() + defer h.mux.Unlock() + return h.requests +} + +func (h *mockSpanHandler) PostSpans(_ context.Context, r *api_v2.PostSpansRequest) (*api_v2.PostSpansResponse, error) { + h.mux.Lock() + defer h.mux.Unlock() + h.requests = append(h.requests, r) + return &api_v2.PostSpansResponse{}, nil +} diff --git a/internal/otel_collector/exporter/jaegerexporter/factory.go b/internal/otel_collector/exporter/jaegerexporter/factory.go new file mode 100644 index 00000000000..ac6e287b0ed --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/factory.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerexporter + +import ( + "context" + "fmt" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "jaeger" +) + +// NewFactory creates a factory for Jaeger exporter +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + TimeoutSettings: exporterhelper.DefaultTimeoutSettings(), + RetrySettings: exporterhelper.DefaultRetrySettings(), + QueueSettings: exporterhelper.DefaultQueueSettings(), + GRPCClientSettings: configgrpc.GRPCClientSettings{ + // We almost read 0 bytes, so no need to tune ReadBufferSize. + WriteBufferSize: 512 * 1024, + }, + } +} + +func createTraceExporter( + _ context.Context, + params component.ExporterCreateParams, + config configmodels.Exporter, +) (component.TracesExporter, error) { + + expCfg := config.(*Config) + if expCfg.Endpoint == "" { + // TODO: Improve error message, see #215 + err := fmt.Errorf( + "%q config requires a non-empty \"endpoint\"", + expCfg.Name()) + return nil, err + } + + exp, err := newTraceExporter(expCfg, params.Logger) + if err != nil { + return nil, err + } + + return exp, nil +} diff --git a/internal/otel_collector/exporter/jaegerexporter/factory_test.go b/internal/otel_collector/exporter/jaegerexporter/factory_test.go new file mode 100644 index 00000000000..2d4233c2395 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/factory_test.go @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configerror" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateMetricsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + params := component.ExporterCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateMetricsExporter(context.Background(), params, cfg) + assert.Error(t, err, configerror.ErrDataTypeIsNotSupported) +} + +func TestCreateInstanceViaFactory(t *testing.T) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig() + + // Default config doesn't have default endpoint so creating from it should + // fail. + params := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateTracesExporter(context.Background(), params, cfg) + assert.NotNil(t, err) + assert.Equal(t, "\"jaeger\" config requires a non-empty \"endpoint\"", err.Error()) + assert.Nil(t, exp) + + // Endpoint doesn't have a default value so set it directly. + expCfg := cfg.(*Config) + expCfg.Endpoint = "some.target.org:12345" + exp, err = factory.CreateTracesExporter(context.Background(), params, cfg) + assert.NoError(t, err) + assert.NotNil(t, exp) + + assert.NoError(t, exp.Shutdown(context.Background())) +} diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/ca.crt b/internal/otel_collector/exporter/jaegerexporter/testdata/ca.crt new file mode 100644 index 00000000000..afe1effdf3a --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CCQCGaM7CuADemjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB +VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM +CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIwMDkyMjA1MjIx +MVoXDTMwMDkyMDA1MjIxMVowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry +YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV +BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AMfzhzDBS/8Pt4nQL25ZgNXqdpAs+mpnOQLc0ep7+HI4zmWyP+3OvKxF4dyCIk5w +wTYZo5ln79hshIU+OGRBBmIrMvP09ekhjuvACYK/Sn4MLj9lihq9wV3+hqrlQlqG +YaZULoQ3+7cj0S964JQu5wfD3IIeSAzaW7EU4x5CBwFlpe9aSqgiENicUmDQRem7 +mXpCKGz0QZ8OPhidSpYYzltOYqEUYVjNwB7oQj5awpuT5twqF3Tzy1UQ8EsT6K8a +k4TdnZAwZoSZmpBP7GNEZAac8YcgAdk4llhB/pc+wd62MODGYznvP/TtU4BiY3K+ +wdf3nPPYd7P2PelqXWfRxp0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAr5tOtnsm +g4Co6vG8lb788+nHMoGCzNG1GxEoluAXfbKvM2zF8m3nt9WY7A16sqBuDrBlAruM +R28hLtVA6nDbpRo2K1bvfI6pBA5DBDxx/bCBPHG49v/MSmpeXrgpX2/8ZQyDqrLe +gmN+NZ55yv95JUHi4rBeI72q+kEXFJ97yNFGbirR22VOjk+L28rA8qDCGcGF1NJf +u/va6BcXnXwKJhtpjzqqdeQJjigDEmpzVJcPj7PtuKcqjmoeLb46+SRUtSPDGqlS +cqz1Mp0eM6CxS5hfCMZN19K6kTwyy7j2PjnnFWfm4Vx/j24bu7dRMTIgBafFnz8/ +bX5JU6zb1ksWuQ== +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/client.crt b/internal/otel_collector/exporter/jaegerexporter/testdata/client.crt new file mode 100644 index 00000000000..9a34430b39b --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdfMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjExWhcNMzAwOTIwMDUyMjExWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEApZ17aiCiS105m2PajvPyh993oe2PG2FsReihmffWj8kF2YFeDo4weJ7o +SUylnBOwSF0c7VAnQgn6NON+57vVmFm/xVBYvLPqElJq98fJI/AlZTWsFwVfnhWk +T6o9C1L6LmrDe7aduj/CP11GKQsioK1NkFxLYBUTs4k4+kFxT0jhnLpbI1Ib/m6P +VhbRDOcP0lHkF80YrcJbuVop6gobdxq0slCdOjRBHwcIIgZnDZzwVoxEI2zGfX+h +YgP7zcAW8QrGHgsT0xbVC5OA/Re/t3lzJkCNJPLDKQCuUuXsYtLjV0dxLW8sruTT +xQrwG0Rp6iYLAUfGwVFDqNmknP+3vQIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQB+ +1Qp4ny36KgSCfao8NwfvNSDz8VfzaGJQLvJLrq2+YDmUB+i8M4BvivYsz1Q5WQYN +A9dWnMAjgYFItUMBPGoCMlg8panGYS/7XIHXhexENzfqXA0l4bTQt804mz+QIbs4 +oqVTtQdLt1OGb8IGWvI+jJGcV08aJ87kdNdd6sTfzwYR0w5uR7ceB5iB6Im5tNgz +7ASqHZYQNpID7mnVK8es2wLoGkh1SI4jRHxZAGBS4uDNy2H2eC1Jj9Y1m5nzl9nA +9IXxwac7f1WfPjpI000Hed3kY7hBV+lIJlF0A29l7DqnVsXKx6FAtEHMwIazAyFF +dTuYHlKIgx2Fno1b35qA +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/client.key b/internal/otel_collector/exporter/jaegerexporter/testdata/client.key new file mode 100644 index 00000000000..4e6369105ec --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApZ17aiCiS105m2PajvPyh993oe2PG2FsReihmffWj8kF2YFe +Do4weJ7oSUylnBOwSF0c7VAnQgn6NON+57vVmFm/xVBYvLPqElJq98fJI/AlZTWs +FwVfnhWkT6o9C1L6LmrDe7aduj/CP11GKQsioK1NkFxLYBUTs4k4+kFxT0jhnLpb +I1Ib/m6PVhbRDOcP0lHkF80YrcJbuVop6gobdxq0slCdOjRBHwcIIgZnDZzwVoxE +I2zGfX+hYgP7zcAW8QrGHgsT0xbVC5OA/Re/t3lzJkCNJPLDKQCuUuXsYtLjV0dx +LW8sruTTxQrwG0Rp6iYLAUfGwVFDqNmknP+3vQIDAQABAoIBAFCAbCz6D+lyNz5B +G0vBaHKDPTOItbcpc+fHXEXrInBh1mYTbBTHKOh41ZBLoXRsXZPPLvBrtal0EBsF +OfqKYxjbB0xx8bBIA89EIJqwkiTV5ld771qBUikVe/j9Vw7aFqHZY4wPCDwwIcuL +Gz94GizgEIPLWRgrJphuazO5+8n9atgrsQkv6iyjYa4b/RiqjJ/cV0NHfdf1rhjA +a67WPunXqRmdthY6znJ5FviVHwyRT3R2rLZ7aeoHwdBCpaqOUtmqF6m3lgR1gTV6 +I8CqenbS7TQNGJJbnBaiHJ4mHkZ7T8raVxbXoEcODx4C5o/XqV8LarTwvEhxoxZf +gU6I3AECgYEA1jXQrN7hFYlUv2f61GMTD/soH6XvusV0Osp8vzt39nnCPFykjygU +Q1HLK5Q2y6IyUrSpRB+ESlY2VRQguLGmPIFYJ7/QMLPbdEmjhzG9pmOwkGPz3Pzo +HDclUk0tVW/Y0yowamGxjwQ2fqYQkDo/x7oJatonLBo/a0d96rpT7rECgYEAxey1 +6wu7Dzp9FqLo2ejmFiEnAo0iJbkWqZ/t970Vss+luN7wdg8pFTmQpcQaP/yULbj7 +Sr4GT+DwvaVp9wOCqmmM7zaiRDsKePh9+x8zDNg93CU46gVrnOXI8lQhy7GU8zJH +vgLIW/lBgWI1WviP8E9RZJ46T1xA8N/b23XP1M0CgYB2IoGuBNDfXriFQuP7I5SM +uLd9FLdsKp8aGTfJAxP6s1WiknkSlkjug3pn0zqQ/SeBoy7Cahs8H+Wg8BPHp/mE +3tRuP4OiiF5b+Iyd75mm3M2wI9+GnVeoz+TeSb/ZoXBTrK28zZntxKYFHLJ/yBAK +pxewfRUjZmpZULkprY7CUQKBgHSS3vL5jcuCX3qdqRfvnNUb4aYXCi29viNaFwb7 +T3Rp6OniJS7j7waZSSDZR7y4P25OrXNwSLdzfVe1vZvHbYaBdIjQXPJi8+AcO9dU +oPTHyGeJZOQxHRjkHl7cPquChIBY9PhtGyVQwcw608Io9F5N2Vqx9xYW+AjnUM8J +rMdhAoGBAMVWRDgVNYyVy/ZBom9xcFOiuDG+U+osE9NZirC+vOhB+hK2NZ+rq7CI +Cf7qVsAYW49Vt0Sk/F+s0FcM//y84rE+hiXOcqFIwXV77mIHcv6QNb4mjdG/kg3+ +5Wx8EjYemS949kbxng8eqoCqyjU8s0v63k5DeCSxpJwX7EzxCOG3 +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/config.yaml b/internal/otel_collector/exporter/jaegerexporter/testdata/config.yaml new file mode 100644 index 00000000000..679c40d3b02 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/config.yaml @@ -0,0 +1,28 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + jaeger: + jaeger/2: + endpoint: "a.new.target:1234" + balancer_name: "round_robin" + timeout: 10s + sending_queue: + enabled: true + num_consumers: 2 + queue_size: 10 + retry_on_failure: + enabled: true + initial_interval: 10s + max_interval: 60s + max_elapsed_time: 10m + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [jaeger, jaeger/2] diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/server.crt b/internal/otel_collector/exporter/jaegerexporter/testdata/server.crt new file mode 100644 index 00000000000..ad9472180ff --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdeMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjExWhcNMzAwOTIwMDUyMjExWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAri5mBuqnshebSyRY4eyDi5Q3OuUFgekmWd4IXQMH2QC8QSXW5sl7zxcU +cCccjRyBcfCiOhAphD/w3XsuZhVVdVMj5KZErh0gKFyZCJQrkTma6/7L4NsY7R9k +p0YzEaAHCx7DsEpzGfGhstv9CJDRNd7H2ydF27vvc28KvOQn2FSoIGVVYKAQGCiZ +jzq+eaA/KqByeKpU612KJHi0v7fc8VA0XLpnFXH5PqlMV18YqnEJRw8honhPNOEP +cbdDTh06ycChWVEOhZSz8yJWz6M5JdxmMZmgU8QaJhJ8h6n/JsauftH9R8jxRqhe +Xoy113z2+IqEYDm+jzd6+vLwnZLZdQIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCk +XhMNKQe1BqVpuOIFSsAg48VK44tWX/xn9fgeRtNL0b/LWPAuNk17GIu85WmCaiVF +0wha6+fh6cKDw9PNFK3ktXEESa9y0Wnf6jjYA/oj6jAnGI84F0YS+MSm/x5etnW1 +YhCuyFnPTNVoHP7qawQHXP7LC6t/y4EafYohoaGgNhxvCp0n8itfLPHfcZfCQio+ +8RCGM2OukZXihaRGKfEGLYlrAfr0l53n8mxF4Qzp8PiJfB/5FPTnS9teIzhq29nM +62ITmyWvJeHdtqpdOZsEiUo6E1idwtHf3E5XKPuzPA6/Llhrr+JS6kPdq1B8EtN3 +n8FE3N6bXsGCZgoy2S60 +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/server.key b/internal/otel_collector/exporter/jaegerexporter/testdata/server.key new file mode 100644 index 00000000000..32767297214 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAri5mBuqnshebSyRY4eyDi5Q3OuUFgekmWd4IXQMH2QC8QSXW +5sl7zxcUcCccjRyBcfCiOhAphD/w3XsuZhVVdVMj5KZErh0gKFyZCJQrkTma6/7L +4NsY7R9kp0YzEaAHCx7DsEpzGfGhstv9CJDRNd7H2ydF27vvc28KvOQn2FSoIGVV +YKAQGCiZjzq+eaA/KqByeKpU612KJHi0v7fc8VA0XLpnFXH5PqlMV18YqnEJRw8h +onhPNOEPcbdDTh06ycChWVEOhZSz8yJWz6M5JdxmMZmgU8QaJhJ8h6n/JsauftH9 +R8jxRqheXoy113z2+IqEYDm+jzd6+vLwnZLZdQIDAQABAoIBAB9lY6SZhXK3iJlp +ys+mVyvAopfuEikRgoUuXWmOk0qNNGG11V+yL6vraBazJhVVbwu/qS44HZOmBt+V +RY7HB6lnPBAJ3FVaEKLV1gvS0WuS65bgTZWRSJPtVbJFGA10P/DEMdfEA61IfbIE +rz5tBBjmuDWLha8O2CGBgXj80yXOCIJYYcTZizLo2njxbZ6RrcJZxtKoYVxSCwjI ++HY+p5yKl9+P6O1uWf1gRTZzBS6j/IcSncp04zl0QZ05NTlh334AM+9TdLRdBNvW +aGC3gD5SttwBzjeQQUoao2KfTyEL4OZUtDlgGCxFTAr8JmikooOSGr50iz6raPok +oYiXB90CgYEA1d6WO5PDzSp78VxE3CWzvDHK/4mTrBHJzLfSQ/ppAWeyT93Yqo1X +AZrhbTB4qeT/hP/d8tX5lTUXCmxly3RhKGqINtax9NX9nnT3lW/KLJy+fCJlJhGb +/e4zN9TjmHHd1s0F7X0sX2kCT3CgMRZAuRM8HIbUisJuGkxn40vIR68CgYEA0H5W +rB46duLASF/HQes5gG+UWGu8ZjfLRm61dARIE+DF9A1UifzGs4MNRXIBxJMC4eVN +CHYOZAtfaxS67nF+rjCBFJhDK86K208oIOXMlsdsCNEPSQOJ773SiW7obhxHvb/v +arMijMlw6gR6sZjAvDCqLPqAn2novknpTm+p1hsCgYBV7vsUczogjOqCP53Xizqd +6q/zX3c7k2YvYMkW0V4x11W5a51sYiBMn8vmUKybL01QRnMK6NlBD7TzVjzMtDV5 +xNx4lGmqw/UFH2B/5gwpQs3zGOrlkfvI21YvH9ZXYaFOdtDj7QDQUWRBQTLMpnFZ +LBZiK/bozljpLjwsAz41NwKBgD3FgLZkClxY2DFZNzb/kzzLUj9URDBgzXgjqt0h +u50wFlY1cNulKdXbuR3fJkjwaYU2I7mjCKZ5fB7EuJGWzLqdIlFQv24GNru9Wx1M +GG5/zTFbh1TRmJeQPQV8955QAK1mZW/OfSkLMcoT46okoM3g+Tp0SZFxloRyb45O +pRoLAoGASL4xGPKJBTMFgSDmSK+Uyeax+ckn4i0pck9pIi8fahPSe0wEMFAazVPS +4izjuyIH3J5nA0zaDzJccZGvmtL1d+u5irtTPvuwUcpGW7uCupG2kh7rQGd94OoZ +z3S8vRpta6EiyS/qmLl1PBvPzOJltm2+LtqwRxLTQPr+Xz7HL9g= +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/exporter/jaegerexporter/testdata/test_cert.pem b/internal/otel_collector/exporter/jaegerexporter/testdata/test_cert.pem new file mode 100644 index 00000000000..b2e77b89d49 --- /dev/null +++ b/internal/otel_collector/exporter/jaegerexporter/testdata/test_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE6jCCAtICCQDVU4PtqpqADTANBgkqhkiG9w0BAQsFADA3MQswCQYDVQQGEwJV +UzETMBEGA1UECAwKY2FsaWZvcm5pYTETMBEGA1UECgwKb3BlbmNlbnN1czAeFw0x +OTAzMDQxODA3MjZaFw0yMDAzMDMxODA3MjZaMDcxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApjYWxpZm9ybmlhMRMwEQYDVQQKDApvcGVuY2Vuc3VzMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAy9JQiAOMzArcdiS4szbTuzg5yYijSSY6SvGj +XMs4/LEFLxgGmFfyHXxoVQzV26lTu/AiUFlZi4JY2qlkZyPwmmmSg4fmzikpVPiC +Vv9pvSIojs8gs0sHaOt40Q8ym43bNt3Mh8rYrs+XMERi6Ol9//j4LnfePkNU5uEo +qC8KQamckaMR6UEHFNunyOwvNBsipgTPldQUPGVnCsNKk8olYGAXS7DR25bgbPli +4T9VCSElsSPAODmyo+2MEDagVXa1vVYxKyO2k6oeBS0lsvdRqRTmGggcg0B/dk+a +H1CL9ful0cu9P3dQif+hfGay8udPkwDLPEq1+WnjJFut3Pmbk3SqUCas5iWt76kK +eKFh4k8fCy4yiaZxzvSbm9+bEBHAl0ZXd8pjvAsBfCKe6G9SBzE1DK4FjWiiEGCb +5dGsyTKr33q3DekLvT3LF8ZeON/13d9toucX9PqG2HDwMP/Fb4WjQIzOc/H9wIak +pf7u6QBDGUiCMmoDrp1d8RsI1RPbEhoywH0YlLmwgf+cr1dU7vlISf576EsGxFz4 ++/sZjIBvZBHn/x0MH+bs4J8V3vMujfDoRdhL07bK7q/AkEALUxljKEfoWeqiuVzK +F9BVv3xNhiua2kgPVbMNWPrQ5uotkNp8IykJ3QOuQ3p5pzxdGfpLd6f8gmJDmcbi +AI9dWTcCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVVi4t/Sumre+AGTaU7np9dl2 +tpllbES5ixe6m2uezt5wAzYNNyuQ2mMG2XrSkMy5gvBZRT9nRNSmLV8VEcxZihG0 +YHS5soXnLL3Jdlwxp98WTDPvM1ntxcHyEyqrrg9YDfKn4sOrr5vo2yZzoKwtxtc7 +lue9JormVx7GxMi7NwaUtCbnwAIcqJJpFjt1EhmJOxGqTJPgUvTBdeGvRj30c6fk +pqpUdPbZ7RKPEtbLoMoCBujKnErv+H0G6Vp9WyCHN+Mi9uTMsGwH14cmJjmfwGDC +8/WF4LdlawFnf/arIp9YcVwcP91d4ywyvbuuo2M7qdosQ7k4uRZ3tyggLYShS3RW +BMEhMRDz9dM0oKGF+HnaS824BIh6O6Hn82Vt8uCKS7IbEX99/kkN1KcqqQe6Lwjq +tG/lm4K5yf+FJVDivpZ9mYTvqTBjhTaOp6m3HYSNJfS0hLQVvEuBNXd8bHiXkcLp +rmFOYUWsjxV1Qku3U5Rner0UpB2Fuw9nJcXuDgWG0gjwzAZ83y3du1VIZp0Ad8Vv +IYpaucbImGJszMtNXn3l72K1wvQVIhm9eRwYc3QteJzweHaDsbytZEoS/GhTrZIT +wRe5ZGrjJBJngRANRSm1BH8j6PjLem9mzPb2eytwJJA0lLhUk4vYproVvXcx0vow +5F+5VB1YB8/tbWePmpo= +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/kafkaexporter/README.md b/internal/otel_collector/exporter/kafkaexporter/README.md new file mode 100644 index 00000000000..b75fcbfe63a --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/README.md @@ -0,0 +1,69 @@ +# Kafka Exporter + +Kafka exporter exports traces to Kafka. This exporter uses a synchronous producer +that blocks and does not batch messages, therefore it should be used with batch and queued retry +processors for higher throughput and resiliency. Message payload encoding is configurable. + +The following settings are required: +- `protocol_version` (no default): Kafka protocol version e.g. 2.0.0 + +The following settings can be optionally configured: +- `brokers` (default = localhost:9092): The list of kafka brokers +- `topic` (default = otlp_spans for traces, otlp_metrics for metrics): The name of the kafka topic to export to. +- `encoding` (default = otlp_proto): The encoding of the traces sent to kafka. All available encodings: + - `otlp_proto`: payload is Protobuf serialized from `ExportTraceServiceRequest` if set as a traces exporter or `ExportMetricsServiceRequest` for metrics. + - The following encodings are valid *only* for **traces**. + - `jaeger_proto`: the payload is serialized to a single Jaeger proto `Span`. + - `jaeger_json`: the payload is serialized to a single Jaeger JSON Span using `jsonpb`. +- `auth` + - `plain_text` + - `username`: The username to use. + - `password`: The password to use + - `tls` + - `ca_file`: path to the CA cert. For a client this verifies the server certificate. Should + only be used if `insecure` is set to true. + - `cert_file`: path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to true. + - `key_file`: path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to true. + - `insecure` (default = false): Disable verifying the server's certificate chain and host + name (`InsecureSkipVerify` in the tls config) + - `server_name_override`: ServerName indicates the name of the server requested by the client + in order to support virtual hosting. + - `kerberos` + - `service_name`: Kerberos service name + - `realm`: Kerberos realm + - `use_keytab`: Use of keytab instead of password, if this is true, keytab file will be used instead of password + - `username`: The Kerberos username used for authenticate with KDC + - `password`: The Kerberos password used for authenticate with KDC + - `config_file`: Path to Kerberos configuration. i.e /etc/krb5.conf + - `keytab_file`: Path to keytab file. i.e /etc/security/kafka.keytab +- `metadata` + - `full` (default = true): Whether to maintain a full set of metadata. + When disabled the client does not make the initial request to broker at the startup. + - `retry` + - `max` (default = 3): The number of retries to get metadata + - `backoff` (default = 250ms): How long to wait between metadata retries +- `timeout` (default = 5s): Is the timeout for every attempt to send data to the backend. +- `retry_on_failure` + - `enabled` (default = true) + - `initial_interval` (default = 5s): Time to wait after the first failure before retrying; ignored if `enabled` is `false` + - `max_interval` (default = 30s): Is the upper bound on backoff; ignored if `enabled` is `false` + - `max_elapsed_time` (default = 120s): Is the maximum amount of time spent trying to send a batch; ignored if `enabled` is `false` +- `sending_queue` + - `enabled` (default = true) + - `num_consumers` (default = 10): Number of consumers that dequeue batches; ignored if `enabled` is `false` + - `queue_size` (default = 5000): Maximum number of batches kept in memory before dropping data; ignored if `enabled` is `false`; + User should calculate this as `num_seconds * requests_per_second` where: + - `num_seconds` is the number of seconds to buffer in case of a backend outage + - `requests_per_second` is the average number of requests per seconds. + +Example configuration: + +```yaml +exporters: + kafka: + brokers: + - localhost:9092 + protocol_version: 2.0.0 +``` diff --git a/internal/otel_collector/exporter/kafkaexporter/authentication.go b/internal/otel_collector/exporter/kafkaexporter/authentication.go new file mode 100644 index 00000000000..ad723b4696b --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/authentication.go @@ -0,0 +1,95 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "fmt" + + "github.com/Shopify/sarama" + + "go.opentelemetry.io/collector/config/configtls" +) + +// Authentication defines authentication. +type Authentication struct { + PlainText *PlainTextConfig `mapstructure:"plain_text"` + TLS *configtls.TLSClientSetting `mapstructure:"tls"` + Kerberos *KerberosConfig `mapstructure:"kerberos"` +} + +// PlainTextConfig defines plaintext authentication. +type PlainTextConfig struct { + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` +} + +// KerberosConfig defines kereros configuration. +type KerberosConfig struct { + ServiceName string `mapstructure:"service_name"` + Realm string `mapstructure:"realm"` + UseKeyTab bool `mapstructure:"use_keytab"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password" json:"-"` + ConfigPath string `mapstructure:"config_file"` + KeyTabPath string `mapstructure:"keytab_file"` +} + +// ConfigureAuthentication configures authentication in sarama.Config. +func ConfigureAuthentication(config Authentication, saramaConfig *sarama.Config) error { + if config.PlainText != nil { + configurePlaintext(*config.PlainText, saramaConfig) + } + if config.TLS != nil { + if err := configureTLS(*config.TLS, saramaConfig); err != nil { + return err + } + } + if config.Kerberos != nil { + configureKerberos(*config.Kerberos, saramaConfig) + } + return nil +} + +func configurePlaintext(config PlainTextConfig, saramaConfig *sarama.Config) { + saramaConfig.Net.SASL.Enable = true + saramaConfig.Net.SASL.User = config.Username + saramaConfig.Net.SASL.Password = config.Password +} + +func configureTLS(config configtls.TLSClientSetting, saramaConfig *sarama.Config) error { + tlsConfig, err := config.LoadTLSConfig() + if err != nil { + return fmt.Errorf("error loading tls config: %w", err) + } + saramaConfig.Net.TLS.Enable = true + saramaConfig.Net.TLS.Config = tlsConfig + return nil +} + +func configureKerberos(config KerberosConfig, saramaConfig *sarama.Config) { + saramaConfig.Net.SASL.Mechanism = sarama.SASLTypeGSSAPI + saramaConfig.Net.SASL.Enable = true + if config.UseKeyTab { + saramaConfig.Net.SASL.GSSAPI.KeyTabPath = config.KeyTabPath + saramaConfig.Net.SASL.GSSAPI.AuthType = sarama.KRB5_KEYTAB_AUTH + } else { + saramaConfig.Net.SASL.GSSAPI.AuthType = sarama.KRB5_USER_AUTH + saramaConfig.Net.SASL.GSSAPI.Password = config.Password + } + saramaConfig.Net.SASL.GSSAPI.KerberosConfigPath = config.ConfigPath + saramaConfig.Net.SASL.GSSAPI.Username = config.Username + saramaConfig.Net.SASL.GSSAPI.Realm = config.Realm + saramaConfig.Net.SASL.GSSAPI.ServiceName = config.ServiceName +} diff --git a/internal/otel_collector/exporter/kafkaexporter/authentication_test.go b/internal/otel_collector/exporter/kafkaexporter/authentication_test.go new file mode 100644 index 00000000000..2dfa0746884 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/authentication_test.go @@ -0,0 +1,93 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "testing" + + "github.com/Shopify/sarama" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configtls" +) + +func TestAuthentication(t *testing.T) { + saramaPlaintext := &sarama.Config{} + saramaPlaintext.Net.SASL.Enable = true + saramaPlaintext.Net.SASL.User = "jdoe" + saramaPlaintext.Net.SASL.Password = "pass" + + saramaTLSCfg := &sarama.Config{} + saramaTLSCfg.Net.TLS.Enable = true + tlsClient := configtls.TLSClientSetting{} + tlscfg, err := tlsClient.LoadTLSConfig() + require.NoError(t, err) + saramaTLSCfg.Net.TLS.Config = tlscfg + + saramaKerberosCfg := &sarama.Config{} + saramaKerberosCfg.Net.SASL.Mechanism = sarama.SASLTypeGSSAPI + saramaKerberosCfg.Net.SASL.Enable = true + saramaKerberosCfg.Net.SASL.GSSAPI.ServiceName = "foobar" + saramaKerberosCfg.Net.SASL.GSSAPI.AuthType = sarama.KRB5_USER_AUTH + + saramaKerberosKeyTabCfg := &sarama.Config{} + saramaKerberosKeyTabCfg.Net.SASL.Mechanism = sarama.SASLTypeGSSAPI + saramaKerberosKeyTabCfg.Net.SASL.Enable = true + saramaKerberosKeyTabCfg.Net.SASL.GSSAPI.KeyTabPath = "/path" + saramaKerberosKeyTabCfg.Net.SASL.GSSAPI.AuthType = sarama.KRB5_KEYTAB_AUTH + + tests := []struct { + auth Authentication + saramaConfig *sarama.Config + err string + }{ + { + auth: Authentication{PlainText: &PlainTextConfig{Username: "jdoe", Password: "pass"}}, + saramaConfig: saramaPlaintext, + }, + { + auth: Authentication{TLS: &configtls.TLSClientSetting{}}, + saramaConfig: saramaTLSCfg, + }, + { + auth: Authentication{TLS: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{CAFile: "/doesnotexists"}, + }}, + saramaConfig: saramaTLSCfg, + err: "failed to load TLS config", + }, + { + auth: Authentication{Kerberos: &KerberosConfig{ServiceName: "foobar"}}, + saramaConfig: saramaKerberosCfg, + }, + { + auth: Authentication{Kerberos: &KerberosConfig{UseKeyTab: true, KeyTabPath: "/path"}}, + saramaConfig: saramaKerberosKeyTabCfg, + }, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + config := &sarama.Config{} + err := ConfigureAuthentication(test.auth, config) + if test.err != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), test.err) + } else { + assert.Equal(t, test.saramaConfig, config) + } + }) + } +} diff --git a/internal/otel_collector/exporter/kafkaexporter/config.go b/internal/otel_collector/exporter/kafkaexporter/config.go new file mode 100644 index 00000000000..3cd98f28428 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/config.go @@ -0,0 +1,71 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "time" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// Config defines configuration for Kafka exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` + exporterhelper.TimeoutSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + // The list of kafka brokers (default localhost:9092) + Brokers []string `mapstructure:"brokers"` + // Kafka protocol version + ProtocolVersion string `mapstructure:"protocol_version"` + // The name of the kafka topic to export to (default otlp_spans for traces, otlp_metrics for metrics) + Topic string `mapstructure:"topic"` + + // Encoding of messages (default "otlp_proto") + Encoding string `mapstructure:"encoding"` + + // Metadata is the namespace for metadata management properties used by the + // Client, and shared by the Producer/Consumer. + Metadata Metadata `mapstructure:"metadata"` + + // Authentication defines used authentication mechanism. + Authentication Authentication `mapstructure:"auth"` +} + +// Metadata defines configuration for retrieving metadata from the broker. +type Metadata struct { + // Whether to maintain a full set of metadata for all topics, or just + // the minimal set that has been necessary so far. The full set is simpler + // and usually more convenient, but can take up a substantial amount of + // memory if you have many topics and partitions. Defaults to true. + Full bool `mapstructure:"full"` + + // Retry configuration for metadata. + // This configuration is useful to avoid race conditions when broker + // is starting at the same time as collector. + Retry MetadataRetry `mapstructure:"retry"` +} + +// MetadataRetry defines retry configuration for Metadata. +type MetadataRetry struct { + // The total number of times to retry a metadata request when the + // cluster is in the middle of a leader election or at startup (default 3). + Max int `mapstructure:"max"` + // How long to wait for leader election to occur before retrying + // (default 250ms). Similar to the JVM's `retry.backoff.ms`. + Backoff time.Duration `mapstructure:"backoff"` +} diff --git a/internal/otel_collector/exporter/kafkaexporter/config_test.go b/internal/otel_collector/exporter/kafkaexporter/config_test.go new file mode 100644 index 00000000000..4382e6cdf37 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/config_test.go @@ -0,0 +1,78 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + require.NoError(t, err) + require.Equal(t, 1, len(cfg.Receivers)) + + c := cfg.Exporters[typeStr].(*Config) + assert.Equal(t, &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + TimeoutSettings: exporterhelper.TimeoutSettings{ + Timeout: 10 * time.Second, + }, + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 10 * time.Second, + MaxInterval: 1 * time.Minute, + MaxElapsedTime: 10 * time.Minute, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 2, + QueueSize: 10, + }, + Topic: "spans", + Encoding: "otlp_proto", + Brokers: []string{"foo:123", "bar:456"}, + Authentication: Authentication{ + PlainText: &PlainTextConfig{ + Username: "jdoe", + Password: "pass", + }, + }, + Metadata: Metadata{ + Full: false, + Retry: MetadataRetry{ + Max: 15, + Backoff: defaultMetadataRetryBackoff, + }, + }, + }, c) +} diff --git a/internal/otel_collector/exporter/kafkaexporter/factory.go b/internal/otel_collector/exporter/kafkaexporter/factory.go new file mode 100644 index 00000000000..1064b7715f9 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/factory.go @@ -0,0 +1,144 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + typeStr = "kafka" + defaultTracesTopic = "otlp_spans" + defaultMetricsTopic = "otlp_metrics" + defaultEncoding = "otlp_proto" + defaultBroker = "localhost:9092" + // default from sarama.NewConfig() + defaultMetadataRetryMax = 3 + // default from sarama.NewConfig() + defaultMetadataRetryBackoff = time.Millisecond * 250 + // default from sarama.NewConfig() + defaultMetadataFull = true +) + +// FactoryOption applies changes to kafkaExporterFactory. +type FactoryOption func(factory *kafkaExporterFactory) + +// WithAddTracesMarshallers adds tracesMarshallers. +func WithAddTracesMarshallers(encodingMarshaller map[string]TracesMarshaller) FactoryOption { + return func(factory *kafkaExporterFactory) { + for encoding, marshaller := range encodingMarshaller { + factory.tracesMarshallers[encoding] = marshaller + } + } +} + +// NewFactory creates Kafka exporter factory. +func NewFactory(options ...FactoryOption) component.ExporterFactory { + f := &kafkaExporterFactory{ + tracesMarshallers: tracesMarshallers(), + metricsMarshallers: metricsMarshallers(), + } + for _, o := range options { + o(f) + } + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(f.createTraceExporter), + exporterhelper.WithMetrics(f.createMetricsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + TimeoutSettings: exporterhelper.DefaultTimeoutSettings(), + RetrySettings: exporterhelper.DefaultRetrySettings(), + QueueSettings: exporterhelper.DefaultQueueSettings(), + Brokers: []string{defaultBroker}, + // using an empty topic to track when it has not been set by user, default is based on traces or metrics. + Topic: "", + Encoding: defaultEncoding, + Metadata: Metadata{ + Full: defaultMetadataFull, + Retry: MetadataRetry{ + Max: defaultMetadataRetryMax, + Backoff: defaultMetadataRetryBackoff, + }, + }, + } +} + +type kafkaExporterFactory struct { + tracesMarshallers map[string]TracesMarshaller + metricsMarshallers map[string]MetricsMarshaller +} + +func (f *kafkaExporterFactory) createTraceExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.TracesExporter, error) { + oCfg := cfg.(*Config) + if oCfg.Topic == "" { + oCfg.Topic = defaultTracesTopic + } + exp, err := newTracesExporter(*oCfg, params, f.tracesMarshallers) + if err != nil { + return nil, err + } + return exporterhelper.NewTraceExporter( + cfg, + params.Logger, + exp.traceDataPusher, + // Disable exporterhelper Timeout, because we cannot pass a Context to the Producer, + // and will rely on the sarama Producer Timeout logic. + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings), + exporterhelper.WithShutdown(exp.Close)) +} + +func (f *kafkaExporterFactory) createMetricsExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.MetricsExporter, error) { + oCfg := cfg.(*Config) + if oCfg.Topic == "" { + oCfg.Topic = defaultMetricsTopic + } + exp, err := newMetricsExporter(*oCfg, params, f.metricsMarshallers) + if err != nil { + return nil, err + } + return exporterhelper.NewMetricsExporter( + cfg, + params.Logger, + exp.metricsDataPusher, + // Disable exporterhelper Timeout, because we cannot pass a Context to the Producer, + // and will rely on the sarama Producer Timeout logic. + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings), + exporterhelper.WithShutdown(exp.Close)) +} diff --git a/internal/otel_collector/exporter/kafkaexporter/factory_test.go b/internal/otel_collector/exporter/kafkaexporter/factory_test.go new file mode 100644 index 00000000000..fdc2138968e --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/factory_test.go @@ -0,0 +1,115 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig().(*Config) + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) + assert.Equal(t, []string{defaultBroker}, cfg.Brokers) + assert.Equal(t, "", cfg.Topic) +} + +func TestCreateTracesExporter(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Brokers = []string{"invalid:9092"} + cfg.ProtocolVersion = "2.0.0" + // this disables contacting the broker so we can successfully create the exporter + cfg.Metadata.Full = false + f := kafkaExporterFactory{tracesMarshallers: tracesMarshallers()} + r, err := f.createTraceExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + assert.NotNil(t, r) +} + +func TestCreateMetricsExport(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Brokers = []string{"invalid:9092"} + cfg.ProtocolVersion = "2.0.0" + // this disables contacting the broker so we can successfully create the exporter + cfg.Metadata.Full = false + mf := kafkaExporterFactory{metricsMarshallers: metricsMarshallers()} + mr, err := mf.createMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + assert.NotNil(t, mr) +} + +func TestCreateTracesExporter_err(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Brokers = []string{"invalid:9092"} + cfg.ProtocolVersion = "2.0.0" + f := kafkaExporterFactory{tracesMarshallers: tracesMarshallers()} + r, err := f.createTraceExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + // no available broker + require.Error(t, err) + assert.Nil(t, r) +} + +func TestCreateMetricsExporter_err(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Brokers = []string{"invalid:9092"} + cfg.ProtocolVersion = "2.0.0" + mf := kafkaExporterFactory{metricsMarshallers: metricsMarshallers()} + mr, err := mf.createMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.Error(t, err) + assert.Nil(t, mr) +} + +func TestWithMarshallers(t *testing.T) { + cm := &customMarshaller{} + f := NewFactory(WithAddTracesMarshallers(map[string]TracesMarshaller{cm.Encoding(): cm})) + cfg := createDefaultConfig().(*Config) + // disable contacting broker + cfg.Metadata.Full = false + + t.Run("custom_encoding", func(t *testing.T) { + cfg.Encoding = cm.Encoding() + exporter, err := f.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exporter) + }) + t.Run("default_encoding", func(t *testing.T) { + cfg.Encoding = new(otlpTracesPbMarshaller).Encoding() + exporter, err := f.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + assert.NotNil(t, exporter) + }) +} + +type customMarshaller struct { +} + +var _ TracesMarshaller = (*customMarshaller)(nil) + +func (c customMarshaller) Marshal(_ pdata.Traces) ([]Message, error) { + panic("implement me") +} + +func (c customMarshaller) Encoding() string { + return "custom" +} diff --git a/internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller.go b/internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller.go new file mode 100644 index 00000000000..10d2fc1dad3 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller.go @@ -0,0 +1,98 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "bytes" + + "github.com/gogo/protobuf/jsonpb" + jaegerproto "github.com/jaegertracing/jaeger/model" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer/pdata" + jaegertranslator "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +type jaegerMarshaller struct { + marshaller jaegerSpanMarshaller +} + +var _ TracesMarshaller = (*jaegerMarshaller)(nil) + +func (j jaegerMarshaller) Marshal(traces pdata.Traces) ([]Message, error) { + batches, err := jaegertranslator.InternalTracesToJaegerProto(traces) + if err != nil { + return nil, err + } + var messages []Message + var errs []error + for _, batch := range batches { + for _, span := range batch.Spans { + span.Process = batch.Process + bts, err := j.marshaller.marshall(span) + // continue to process spans that can be serialized + if err != nil { + errs = append(errs, err) + continue + } + messages = append(messages, Message{Value: bts}) + } + } + return messages, componenterror.CombineErrors(errs) +} + +func (j jaegerMarshaller) Encoding() string { + return j.marshaller.encoding() +} + +type jaegerSpanMarshaller interface { + marshall(span *jaegerproto.Span) ([]byte, error) + encoding() string +} + +type jaegerProtoSpanMarshaller struct { +} + +var _ jaegerSpanMarshaller = (*jaegerProtoSpanMarshaller)(nil) + +func (p jaegerProtoSpanMarshaller) marshall(span *jaegerproto.Span) ([]byte, error) { + return span.Marshal() +} + +func (p jaegerProtoSpanMarshaller) encoding() string { + return "jaeger_proto" +} + +type jaegerJSONSpanMarshaller struct { + pbMarshaller *jsonpb.Marshaler +} + +var _ jaegerSpanMarshaller = (*jaegerJSONSpanMarshaller)(nil) + +func newJaegerJSONMarshaller() *jaegerJSONSpanMarshaller { + return &jaegerJSONSpanMarshaller{ + pbMarshaller: &jsonpb.Marshaler{}, + } +} + +func (p jaegerJSONSpanMarshaller) marshall(span *jaegerproto.Span) ([]byte, error) { + out := new(bytes.Buffer) + err := p.pbMarshaller.Marshal(out, span) + return out.Bytes(), err +} + +func (p jaegerJSONSpanMarshaller) encoding() string { + return "jaeger_json" +} diff --git a/internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller_test.go b/internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller_test.go new file mode 100644 index 00000000000..af1a7e27e61 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/jaeger_marshaller_test.go @@ -0,0 +1,95 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "bytes" + "testing" + + "github.com/gogo/protobuf/jsonpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + jaegertranslator "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +func TestJaegerMarshaller(t *testing.T) { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetName("foo") + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetStartTime(pdata.TimestampUnixNano(10)) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetEndTime(pdata.TimestampUnixNano(20)) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetTraceID(pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetSpanID(pdata.NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + batches, err := jaegertranslator.InternalTracesToJaegerProto(td) + require.NoError(t, err) + + batches[0].Spans[0].Process = batches[0].Process + jaegerProtoBytes, err := batches[0].Spans[0].Marshal() + require.NoError(t, err) + require.NotNil(t, jaegerProtoBytes) + + jsonMarshaller := &jsonpb.Marshaler{} + jsonByteBuffer := new(bytes.Buffer) + require.NoError(t, jsonMarshaller.Marshal(jsonByteBuffer, batches[0].Spans[0])) + + tests := []struct { + unmarshaller TracesMarshaller + encoding string + messages []Message + }{ + { + unmarshaller: jaegerMarshaller{ + marshaller: jaegerProtoSpanMarshaller{}, + }, + encoding: "jaeger_proto", + messages: []Message{{Value: jaegerProtoBytes}}, + }, + { + unmarshaller: jaegerMarshaller{ + marshaller: jaegerJSONSpanMarshaller{ + pbMarshaller: &jsonpb.Marshaler{}, + }, + }, + encoding: "jaeger_json", + messages: []Message{{Value: jsonByteBuffer.Bytes()}}, + }, + } + for _, test := range tests { + t.Run(test.encoding, func(t *testing.T) { + messages, err := test.unmarshaller.Marshal(td) + require.NoError(t, err) + assert.Equal(t, test.messages, messages) + assert.Equal(t, test.encoding, test.unmarshaller.Encoding()) + }) + } +} + +func TestJaegerMarshaller_error_covert_traceID(t *testing.T) { + marshaller := jaegerMarshaller{ + marshaller: jaegerProtoSpanMarshaller{}, + } + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + // fails in zero traceID + messages, err := marshaller.Marshal(td) + require.Error(t, err) + assert.Nil(t, messages) +} diff --git a/internal/otel_collector/exporter/kafkaexporter/kafka_exporter.go b/internal/otel_collector/exporter/kafkaexporter/kafka_exporter.go new file mode 100644 index 00000000000..fff86bdb695 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/kafka_exporter.go @@ -0,0 +1,154 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "context" + "fmt" + + "github.com/Shopify/sarama" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" +) + +var errUnrecognizedEncoding = fmt.Errorf("unrecognized encoding") + +// kafkaTracesProducer uses sarama to produce trace messages to Kafka. +type kafkaTracesProducer struct { + producer sarama.SyncProducer + topic string + marshaller TracesMarshaller + logger *zap.Logger +} + +func (e *kafkaTracesProducer) traceDataPusher(_ context.Context, td pdata.Traces) (int, error) { + messages, err := e.marshaller.Marshal(td) + if err != nil { + return td.SpanCount(), consumererror.Permanent(err) + } + err = e.producer.SendMessages(producerMessages(messages, e.topic)) + if err != nil { + return td.SpanCount(), err + } + return 0, nil +} + +func (e *kafkaTracesProducer) Close(context.Context) error { + return e.producer.Close() +} + +// kafkaMetricsProducer uses sarama to produce metrics messages to kafka +type kafkaMetricsProducer struct { + producer sarama.SyncProducer + topic string + marshaller MetricsMarshaller + logger *zap.Logger +} + +func (e *kafkaMetricsProducer) metricsDataPusher(_ context.Context, md pdata.Metrics) (int, error) { + messages, err := e.marshaller.Marshal(md) + if err != nil { + return md.MetricCount(), consumererror.Permanent(err) + } + err = e.producer.SendMessages(producerMessages(messages, e.topic)) + if err != nil { + return md.MetricCount(), err + } + return 0, nil +} + +func (e *kafkaMetricsProducer) Close(context.Context) error { + return e.producer.Close() +} + +func newSaramaProducer(config Config) (sarama.SyncProducer, error) { + c := sarama.NewConfig() + // These setting are required by the sarama.SyncProducer implementation. + c.Producer.Return.Successes = true + c.Producer.Return.Errors = true + // Wait only the local commit to succeed before responding. + c.Producer.RequiredAcks = sarama.WaitForLocal + // Because sarama does not accept a Context for every message, set the Timeout here. + c.Producer.Timeout = config.Timeout + c.Metadata.Full = config.Metadata.Full + c.Metadata.Retry.Max = config.Metadata.Retry.Max + c.Metadata.Retry.Backoff = config.Metadata.Retry.Backoff + if config.ProtocolVersion != "" { + version, err := sarama.ParseKafkaVersion(config.ProtocolVersion) + if err != nil { + return nil, err + } + c.Version = version + } + if err := ConfigureAuthentication(config.Authentication, c); err != nil { + return nil, err + } + producer, err := sarama.NewSyncProducer(config.Brokers, c) + if err != nil { + return nil, err + } + return producer, nil +} + +func newMetricsExporter(config Config, params component.ExporterCreateParams, marshallers map[string]MetricsMarshaller) (*kafkaMetricsProducer, error) { + marshaller := marshallers[config.Encoding] + if marshaller == nil { + return nil, errUnrecognizedEncoding + } + producer, err := newSaramaProducer(config) + if err != nil { + return nil, err + } + + return &kafkaMetricsProducer{ + producer: producer, + topic: config.Topic, + marshaller: marshaller, + logger: params.Logger, + }, nil + +} + +// newTracesExporter creates Kafka exporter. +func newTracesExporter(config Config, params component.ExporterCreateParams, marshallers map[string]TracesMarshaller) (*kafkaTracesProducer, error) { + marshaller := marshallers[config.Encoding] + if marshaller == nil { + return nil, errUnrecognizedEncoding + } + producer, err := newSaramaProducer(config) + if err != nil { + return nil, err + } + return &kafkaTracesProducer{ + producer: producer, + topic: config.Topic, + marshaller: marshaller, + logger: params.Logger, + }, nil +} + +func producerMessages(messages []Message, topic string) []*sarama.ProducerMessage { + producerMessages := make([]*sarama.ProducerMessage, len(messages)) + for i := range messages { + producerMessages[i] = &sarama.ProducerMessage{ + Topic: topic, + Value: sarama.ByteEncoder(messages[i].Value), + } + } + return producerMessages +} diff --git a/internal/otel_collector/exporter/kafkaexporter/kafka_exporter_test.go b/internal/otel_collector/exporter/kafkaexporter/kafka_exporter_test.go new file mode 100644 index 00000000000..6630699a657 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/kafka_exporter_test.go @@ -0,0 +1,218 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "context" + "fmt" + "testing" + + "github.com/Shopify/sarama" + "github.com/Shopify/sarama/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestNewExporter_err_version(t *testing.T) { + c := Config{ProtocolVersion: "0.0.0", Encoding: defaultEncoding} + texp, err := newTracesExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, tracesMarshallers()) + assert.Error(t, err) + assert.Nil(t, texp) +} + +func TestNewExporter_err_encoding(t *testing.T) { + c := Config{Encoding: "foo"} + texp, err := newTracesExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, tracesMarshallers()) + assert.EqualError(t, err, errUnrecognizedEncoding.Error()) + assert.Nil(t, texp) +} + +func TestNewMetricsExporter_err_version(t *testing.T) { + c := Config{ProtocolVersion: "0.0.0", Encoding: defaultEncoding} + mexp, err := newMetricsExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, metricsMarshallers()) + assert.Error(t, err) + assert.Nil(t, mexp) +} + +func TestNewMetricsExporter_err_encoding(t *testing.T) { + c := Config{Encoding: "bar"} + mexp, err := newMetricsExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, metricsMarshallers()) + assert.EqualError(t, err, errUnrecognizedEncoding.Error()) + assert.Nil(t, mexp) +} + +func TestNewMetricsExporter_err_traces_encoding(t *testing.T) { + c := Config{Encoding: "jaeger_proto"} + mexp, err := newMetricsExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, metricsMarshallers()) + assert.EqualError(t, err, errUnrecognizedEncoding.Error()) + assert.Nil(t, mexp) +} + +func TestNewExporter_err_auth_type(t *testing.T) { + c := Config{ + ProtocolVersion: "2.0.0", + Authentication: Authentication{ + TLS: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/doesnotexist", + }, + }, + }, + Encoding: defaultEncoding, + Metadata: Metadata{ + Full: false, + }, + } + texp, err := newTracesExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, tracesMarshallers()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to load TLS config") + assert.Nil(t, texp) + mexp, err := newMetricsExporter(c, component.ExporterCreateParams{Logger: zap.NewNop()}, metricsMarshallers()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to load TLS config") + assert.Nil(t, mexp) +} + +func TestTraceDataPusher(t *testing.T) { + c := sarama.NewConfig() + producer := mocks.NewSyncProducer(t, c) + producer.ExpectSendMessageAndSucceed() + + p := kafkaTracesProducer{ + producer: producer, + marshaller: &otlpTracesPbMarshaller{}, + } + t.Cleanup(func() { + require.NoError(t, p.Close(context.Background())) + }) + droppedSpans, err := p.traceDataPusher(context.Background(), testdata.GenerateTraceDataTwoSpansSameResource()) + require.NoError(t, err) + assert.Equal(t, 0, droppedSpans) +} + +func TestTraceDataPusher_err(t *testing.T) { + c := sarama.NewConfig() + producer := mocks.NewSyncProducer(t, c) + expErr := fmt.Errorf("failed to send") + producer.ExpectSendMessageAndFail(expErr) + + p := kafkaTracesProducer{ + producer: producer, + marshaller: &otlpTracesPbMarshaller{}, + logger: zap.NewNop(), + } + t.Cleanup(func() { + require.NoError(t, p.Close(context.Background())) + }) + td := testdata.GenerateTraceDataTwoSpansSameResource() + droppedSpans, err := p.traceDataPusher(context.Background(), td) + assert.EqualError(t, err, expErr.Error()) + assert.Equal(t, td.SpanCount(), droppedSpans) +} + +func TestTraceDataPusher_marshall_error(t *testing.T) { + expErr := fmt.Errorf("failed to marshall") + p := kafkaTracesProducer{ + marshaller: &tracesErrorMarshaller{err: expErr}, + logger: zap.NewNop(), + } + td := testdata.GenerateTraceDataTwoSpansSameResource() + droppedSpans, err := p.traceDataPusher(context.Background(), td) + require.Error(t, err) + assert.Contains(t, err.Error(), expErr.Error()) + assert.Equal(t, td.SpanCount(), droppedSpans) +} + +func TestMetricsDataPusher(t *testing.T) { + c := sarama.NewConfig() + producer := mocks.NewSyncProducer(t, c) + producer.ExpectSendMessageAndSucceed() + + p := kafkaMetricsProducer{ + producer: producer, + marshaller: &otlpMetricsPbMarshaller{}, + } + t.Cleanup(func() { + require.NoError(t, p.Close(context.Background())) + }) + dropped, err := p.metricsDataPusher(context.Background(), testdata.GenerateMetricsTwoMetrics()) + require.NoError(t, err) + assert.Equal(t, 0, dropped) +} + +func TestMetricsDataPusher_err(t *testing.T) { + c := sarama.NewConfig() + producer := mocks.NewSyncProducer(t, c) + expErr := fmt.Errorf("failed to send") + producer.ExpectSendMessageAndFail(expErr) + + p := kafkaMetricsProducer{ + producer: producer, + marshaller: &otlpMetricsPbMarshaller{}, + logger: zap.NewNop(), + } + t.Cleanup(func() { + require.NoError(t, p.Close(context.Background())) + }) + md := testdata.GenerateMetricsTwoMetrics() + dropped, err := p.metricsDataPusher(context.Background(), md) + assert.EqualError(t, err, expErr.Error()) + assert.Equal(t, md.MetricCount(), dropped) +} + +func TestMetricsDataPusher_marshal_error(t *testing.T) { + expErr := fmt.Errorf("failed to marshall") + p := kafkaMetricsProducer{ + marshaller: &metricsErrorMarshaller{err: expErr}, + logger: zap.NewNop(), + } + md := testdata.GenerateMetricsTwoMetrics() + dropped, err := p.metricsDataPusher(context.Background(), md) + require.Error(t, err) + assert.Contains(t, err.Error(), expErr.Error()) + assert.Equal(t, md.MetricCount(), dropped) +} + +type tracesErrorMarshaller struct { + err error +} + +type metricsErrorMarshaller struct { + err error +} + +func (e metricsErrorMarshaller) Marshal(_ pdata.Metrics) ([]Message, error) { + return nil, e.err +} + +func (e metricsErrorMarshaller) Encoding() string { + panic("implement me") +} + +var _ TracesMarshaller = (*tracesErrorMarshaller)(nil) + +func (e tracesErrorMarshaller) Marshal(_ pdata.Traces) ([]Message, error) { + return nil, e.err +} + +func (e tracesErrorMarshaller) Encoding() string { + panic("implement me") +} diff --git a/internal/otel_collector/exporter/kafkaexporter/marshaller.go b/internal/otel_collector/exporter/kafkaexporter/marshaller.go new file mode 100644 index 00000000000..676dc69969f --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/marshaller.go @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// TracesMarshaller marshals traces into Message array. +type TracesMarshaller interface { + // Marshal serializes spans into Messages + Marshal(traces pdata.Traces) ([]Message, error) + + // Encoding returns encoding name + Encoding() string +} + +// MetricsMarshaller marshals metrics into Message array +type MetricsMarshaller interface { + // Marshal serializes metrics into Messages + Marshal(metrics pdata.Metrics) ([]Message, error) + + // Encoding returns encoding name + Encoding() string +} + +// Message encapsulates Kafka's message payload. +type Message struct { + Value []byte +} + +// tracesMarshallers returns map of supported encodings with TracesMarshaller. +func tracesMarshallers() map[string]TracesMarshaller { + otlppb := &otlpTracesPbMarshaller{} + jaegerProto := jaegerMarshaller{marshaller: jaegerProtoSpanMarshaller{}} + jaegerJSON := jaegerMarshaller{marshaller: newJaegerJSONMarshaller()} + return map[string]TracesMarshaller{ + otlppb.Encoding(): otlppb, + jaegerProto.Encoding(): jaegerProto, + jaegerJSON.Encoding(): jaegerJSON, + } +} + +// metricsMarshallers returns map of supported encodings and MetricsMarshaller +func metricsMarshallers() map[string]MetricsMarshaller { + otlppb := &otlpMetricsPbMarshaller{} + return map[string]MetricsMarshaller{ + otlppb.Encoding(): otlppb, + } +} diff --git a/internal/otel_collector/exporter/kafkaexporter/marshaller_test.go b/internal/otel_collector/exporter/kafkaexporter/marshaller_test.go new file mode 100644 index 00000000000..8c428211317 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/marshaller_test.go @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDefaultTracesMarshallers(t *testing.T) { + expectedEncodings := []string{ + "otlp_proto", + "jaeger_proto", + "jaeger_json", + } + marshallers := tracesMarshallers() + assert.Equal(t, len(expectedEncodings), len(marshallers)) + for _, e := range expectedEncodings { + t.Run(e, func(t *testing.T) { + m, ok := marshallers[e] + require.True(t, ok) + assert.NotNil(t, m) + }) + } +} + +func TestDefaultMetricsMarshallers(t *testing.T) { + expectedEncodings := []string{ + "otlp_proto", + } + marshallers := metricsMarshallers() + assert.Equal(t, len(expectedEncodings), len(marshallers)) + for _, e := range expectedEncodings { + t.Run(e, func(t *testing.T) { + m, ok := marshallers[e] + require.True(t, ok) + assert.NotNil(t, m) + }) + } +} diff --git a/internal/otel_collector/exporter/kafkaexporter/otlp_marshaller.go b/internal/otel_collector/exporter/kafkaexporter/otlp_marshaller.go new file mode 100644 index 00000000000..29232fa2946 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/otlp_marshaller.go @@ -0,0 +1,60 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "go.opentelemetry.io/collector/consumer/pdata" + otlpmetric "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" +) + +var _ TracesMarshaller = (*otlpTracesPbMarshaller)(nil) +var _ MetricsMarshaller = (*otlpMetricsPbMarshaller)(nil) + +type otlpTracesPbMarshaller struct { +} + +func (m *otlpTracesPbMarshaller) Encoding() string { + return defaultEncoding +} + +func (m *otlpTracesPbMarshaller) Marshal(traces pdata.Traces) ([]Message, error) { + request := otlptrace.ExportTraceServiceRequest{ + ResourceSpans: pdata.TracesToOtlp(traces), + } + bts, err := request.Marshal() + if err != nil { + return nil, err + } + return []Message{{Value: bts}}, nil +} + +type otlpMetricsPbMarshaller struct { +} + +func (m *otlpMetricsPbMarshaller) Encoding() string { + return defaultEncoding +} + +func (m *otlpMetricsPbMarshaller) Marshal(metrics pdata.Metrics) ([]Message, error) { + request := otlpmetric.ExportMetricsServiceRequest{ + ResourceMetrics: pdata.MetricsToOtlp(metrics), + } + bts, err := request.Marshal() + if err != nil { + return nil, err + } + return []Message{{Value: bts}}, nil +} diff --git a/internal/otel_collector/exporter/kafkaexporter/otlp_marshaller_test.go b/internal/otel_collector/exporter/kafkaexporter/otlp_marshaller_test.go new file mode 100644 index 00000000000..1b70acdf6a5 --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/otlp_marshaller_test.go @@ -0,0 +1,59 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkaexporter + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + otlpmetric "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestOTLPTracesPbMarshaller(t *testing.T) { + td := testdata.GenerateTraceDataTwoSpansSameResource() + request := &otlptrace.ExportTraceServiceRequest{ + ResourceSpans: pdata.TracesToOtlp(td), + } + expected, err := request.Marshal() + require.NoError(t, err) + require.NotNil(t, expected) + + m := otlpTracesPbMarshaller{} + assert.Equal(t, "otlp_proto", m.Encoding()) + messages, err := m.Marshal(td) + require.NoError(t, err) + assert.Equal(t, []Message{{Value: expected}}, messages) +} + +func TestOTLPMetricsPbMarshaller(t *testing.T) { + md := testdata.GenerateMetricsTwoMetrics() + request := &otlpmetric.ExportMetricsServiceRequest{ + ResourceMetrics: pdata.MetricsToOtlp(md), + } + expected, err := request.Marshal() + require.NoError(t, err) + require.NotNil(t, expected) + + m := otlpMetricsPbMarshaller{} + assert.Equal(t, "otlp_proto", m.Encoding()) + messages, err := m.Marshal(md) + require.NoError(t, err) + assert.Equal(t, []Message{{Value: expected}}, messages) +} diff --git a/internal/otel_collector/exporter/kafkaexporter/testdata/config.yaml b/internal/otel_collector/exporter/kafkaexporter/testdata/config.yaml new file mode 100644 index 00000000000..3c8843b496f --- /dev/null +++ b/internal/otel_collector/exporter/kafkaexporter/testdata/config.yaml @@ -0,0 +1,37 @@ +exporters: + kafka: + topic: spans + brokers: + - "foo:123" + - "bar:456" + metadata: + full: false + retry: + max: 15 + timeout: 10s + auth: + plain_text: + username: jdoe + password: pass + sending_queue: + enabled: true + num_consumers: 2 + queue_size: 10 + retry_on_failure: + enabled: true + initial_interval: 10s + max_interval: 60s + max_elapsed_time: 10m + +processors: + exampleprocessor: + +receivers: + examplereceiver: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [kafka] diff --git a/internal/otel_collector/exporter/loggingexporter/README.md b/internal/otel_collector/exporter/loggingexporter/README.md new file mode 100644 index 00000000000..1ad42f63daa --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/README.md @@ -0,0 +1,29 @@ +# Logging Exporter + +Exports data to the console via zap.Logger. + +Supported pipeline types: traces, metrics, logs + +## Getting Started + +The following settings are optional: + +- `loglevel` (default = `info`): the log level of the logging export + (debug|info|warn|error). When set to `debug`, pipeline data is verbosely + logged. +- `sampling_initial` (default = `2`): number of messages initially logged each + second. +- `sampling_thereafter` (default = `500`): sampling rate after the initial + messages are logged (every Mth message is logged). Refer to [Zap + docs](https://godoc.org/go.uber.org/zap/zapcore#NewSampler) for more details. + on how sampling parameters impact number of messages. + +Example: + +```yaml +exporters: + logging: + loglevel: debug + sampling_initial: 5 + sampling_thereafter: 200 +``` diff --git a/internal/otel_collector/exporter/loggingexporter/config.go b/internal/otel_collector/exporter/loggingexporter/config.go new file mode 100644 index 00000000000..204c81d5145 --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/config.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggingexporter + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for logging exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + + // LogLevel defines log level of the logging exporter; options are debug, info, warn, error. + LogLevel string `mapstructure:"loglevel"` + + // SamplingInitial defines how many samples are initially logged during each second. + SamplingInitial int `mapstructure:"sampling_initial"` + + // SamplingThereafter defines the sampling rate after the initial samples are logged. + SamplingThereafter int `mapstructure:"sampling_thereafter"` +} diff --git a/internal/otel_collector/exporter/loggingexporter/config_test.go b/internal/otel_collector/exporter/loggingexporter/config_test.go new file mode 100644 index 00000000000..6213b1dff22 --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/config_test.go @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggingexporter + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["logging"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["logging/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "logging/2", + TypeVal: "logging", + }, + LogLevel: "debug", + SamplingInitial: 10, + SamplingThereafter: 50, + }) +} diff --git a/internal/otel_collector/exporter/loggingexporter/factory.go b/internal/otel_collector/exporter/loggingexporter/factory.go new file mode 100644 index 00000000000..36c203e35c9 --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/factory.go @@ -0,0 +1,111 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggingexporter + +import ( + "context" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "logging" + defaultSamplingInitial = 2 + defaultSamplingThereafter = 500 +) + +// NewFactory creates a factory for Logging exporter +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter), + exporterhelper.WithMetrics(createMetricsExporter), + exporterhelper.WithLogs(createLogsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + LogLevel: "info", + SamplingInitial: defaultSamplingInitial, + SamplingThereafter: defaultSamplingThereafter, + } +} + +func createTraceExporter(_ context.Context, _ component.ExporterCreateParams, config configmodels.Exporter) (component.TracesExporter, error) { + cfg := config.(*Config) + + exporterLogger, err := createLogger(cfg) + if err != nil { + return nil, err + } + + return newTraceExporter(config, cfg.LogLevel, exporterLogger) +} + +func createMetricsExporter(_ context.Context, _ component.ExporterCreateParams, config configmodels.Exporter) (component.MetricsExporter, error) { + cfg := config.(*Config) + + exporterLogger, err := createLogger(cfg) + if err != nil { + return nil, err + } + + return newMetricsExporter(config, cfg.LogLevel, exporterLogger) +} + +func createLogsExporter(_ context.Context, _ component.ExporterCreateParams, config configmodels.Exporter) (component.LogsExporter, error) { + cfg := config.(*Config) + + exporterLogger, err := createLogger(cfg) + if err != nil { + return nil, err + } + + return newLogsExporter(config, cfg.LogLevel, exporterLogger) +} + +func createLogger(cfg *Config) (*zap.Logger, error) { + var level zapcore.Level + err := (&level).UnmarshalText([]byte(cfg.LogLevel)) + if err != nil { + return nil, err + } + + // We take development config as the base since it matches the purpose + // of logging exporter being used for debugging reasons (so e.g. console encoder) + conf := zap.NewDevelopmentConfig() + conf.Level = zap.NewAtomicLevelAt(level) + conf.Sampling = &zap.SamplingConfig{ + Initial: cfg.SamplingInitial, + Thereafter: cfg.SamplingThereafter, + } + + logginglogger, err := conf.Build() + if err != nil { + return nil, err + } + return logginglogger, nil +} diff --git a/internal/otel_collector/exporter/loggingexporter/factory_test.go b/internal/otel_collector/exporter/loggingexporter/factory_test.go new file mode 100644 index 00000000000..bf04ef5d544 --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/factory_test.go @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggingexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateMetricsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + me, err := factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + assert.NoError(t, err) + assert.NotNil(t, me) +} + +func TestCreateTraceExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + te, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + assert.NoError(t, err) + assert.NotNil(t, te) +} + +func TestCreateLogsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + te, err := factory.CreateLogsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + assert.NoError(t, err) + assert.NotNil(t, te) +} diff --git a/internal/otel_collector/exporter/loggingexporter/logging_exporter.go b/internal/otel_collector/exporter/loggingexporter/logging_exporter.go new file mode 100644 index 00000000000..fbc1dd7e8cd --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/logging_exporter.go @@ -0,0 +1,508 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggingexporter + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "syscall" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +type logDataBuffer struct { + str strings.Builder +} + +func (b *logDataBuffer) logEntry(format string, a ...interface{}) { + b.str.WriteString(fmt.Sprintf(format, a...)) + b.str.WriteString("\n") +} + +func (b *logDataBuffer) logAttr(label string, value string) { + b.logEntry(" %-15s: %s", label, value) +} + +func (b *logDataBuffer) logAttributeMap(label string, am pdata.AttributeMap) { + if am.Len() == 0 { + return + } + + b.logEntry("%s:", label) + am.ForEach(func(k string, v pdata.AttributeValue) { + b.logEntry(" -> %s: %s(%s)", k, v.Type().String(), attributeValueToString(v)) + }) +} + +func (b *logDataBuffer) logStringMap(description string, sm pdata.StringMap) { + if sm.Len() == 0 { + return + } + + b.logEntry("%s:", description) + sm.ForEach(func(k string, v string) { + b.logEntry(" -> %s: %s", k, v) + }) +} + +func (b *logDataBuffer) logInstrumentationLibrary(il pdata.InstrumentationLibrary) { + b.logEntry( + "InstrumentationLibrary %s %s", + il.Name(), + il.Version()) +} + +func (b *logDataBuffer) logMetricDescriptor(md pdata.Metric) { + b.logEntry("Descriptor:") + b.logEntry(" -> Name: %s", md.Name()) + b.logEntry(" -> Description: %s", md.Description()) + b.logEntry(" -> Unit: %s", md.Unit()) + b.logEntry(" -> DataType: %s", md.DataType().String()) +} + +func (b *logDataBuffer) logMetricDataPoints(m pdata.Metric) { + switch m.DataType() { + case pdata.MetricDataTypeNone: + return + case pdata.MetricDataTypeIntGauge: + b.logIntDataPoints(m.IntGauge().DataPoints()) + case pdata.MetricDataTypeDoubleGauge: + b.logDoubleDataPoints(m.DoubleGauge().DataPoints()) + case pdata.MetricDataTypeIntSum: + data := m.IntSum() + b.logEntry(" -> IsMonotonic: %t", data.IsMonotonic()) + b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String()) + b.logIntDataPoints(data.DataPoints()) + case pdata.MetricDataTypeDoubleSum: + data := m.DoubleSum() + b.logEntry(" -> IsMonotonic: %t", data.IsMonotonic()) + b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String()) + b.logDoubleDataPoints(data.DataPoints()) + case pdata.MetricDataTypeIntHistogram: + data := m.IntHistogram() + b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String()) + b.logIntHistogramDataPoints(data.DataPoints()) + case pdata.MetricDataTypeDoubleHistogram: + data := m.DoubleHistogram() + b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String()) + b.logDoubleHistogramDataPoints(data.DataPoints()) + case pdata.MetricDataTypeDoubleSummary: + data := m.DoubleSummary() + b.logDoubleSummaryDataPoints(data.DataPoints()) + } +} + +func (b *logDataBuffer) logIntDataPoints(ps pdata.IntDataPointSlice) { + for i := 0; i < ps.Len(); i++ { + p := ps.At(i) + b.logEntry("IntDataPoints #%d", i) + b.logDataPointLabels(p.LabelsMap()) + + b.logEntry("StartTime: %d", p.StartTime()) + b.logEntry("Timestamp: %d", p.Timestamp()) + b.logEntry("Value: %d", p.Value()) + } +} + +func (b *logDataBuffer) logDoubleDataPoints(ps pdata.DoubleDataPointSlice) { + for i := 0; i < ps.Len(); i++ { + p := ps.At(i) + b.logEntry("DoubleDataPoints #%d", i) + b.logDataPointLabels(p.LabelsMap()) + + b.logEntry("StartTime: %d", p.StartTime()) + b.logEntry("Timestamp: %d", p.Timestamp()) + b.logEntry("Value: %f", p.Value()) + } +} + +func (b *logDataBuffer) logDoubleHistogramDataPoints(ps pdata.DoubleHistogramDataPointSlice) { + for i := 0; i < ps.Len(); i++ { + p := ps.At(i) + b.logEntry("HistogramDataPoints #%d", i) + b.logDataPointLabels(p.LabelsMap()) + + b.logEntry("StartTime: %d", p.StartTime()) + b.logEntry("Timestamp: %d", p.Timestamp()) + b.logEntry("Count: %d", p.Count()) + b.logEntry("Sum: %f", p.Sum()) + + bounds := p.ExplicitBounds() + if len(bounds) != 0 { + for i, bound := range bounds { + b.logEntry("ExplicitBounds #%d: %f", i, bound) + } + } + + buckets := p.BucketCounts() + if len(buckets) != 0 { + for j, bucket := range buckets { + b.logEntry("Buckets #%d, Count: %d", j, bucket) + } + } + } +} + +func (b *logDataBuffer) logIntHistogramDataPoints(ps pdata.IntHistogramDataPointSlice) { + for i := 0; i < ps.Len(); i++ { + p := ps.At(i) + b.logEntry("HistogramDataPoints #%d", i) + b.logDataPointLabels(p.LabelsMap()) + + b.logEntry("StartTime: %d", p.StartTime()) + b.logEntry("Timestamp: %d", p.Timestamp()) + b.logEntry("Count: %d", p.Count()) + b.logEntry("Sum: %d", p.Sum()) + + bounds := p.ExplicitBounds() + if len(bounds) != 0 { + for i, bound := range bounds { + b.logEntry("ExplicitBounds #%d: %f", i, bound) + } + } + + buckets := p.BucketCounts() + if len(buckets) != 0 { + for j, bucket := range buckets { + b.logEntry("Buckets #%d, Count: %d", j, bucket) + } + } + } +} + +func (b *logDataBuffer) logDoubleSummaryDataPoints(ps pdata.DoubleSummaryDataPointSlice) { + for i := 0; i < ps.Len(); i++ { + p := ps.At(i) + b.logEntry("SummaryDataPoints #%d", i) + b.logDataPointLabels(p.LabelsMap()) + + b.logEntry("StartTime: %d", p.StartTime()) + b.logEntry("Timestamp: %d", p.Timestamp()) + b.logEntry("Count: %d", p.Count()) + b.logEntry("Sum: %f", p.Sum()) + + quantiles := p.QuantileValues() + for i := 0; i < quantiles.Len(); i++ { + quantile := quantiles.At(i) + b.logEntry("QuantileValue #%d: Quantile %f, Value %f", i, quantile.Quantile(), quantile.Value()) + } + } +} + +func (b *logDataBuffer) logDataPointLabels(labels pdata.StringMap) { + b.logStringMap("Data point labels", labels) +} + +func (b *logDataBuffer) logLogRecord(lr pdata.LogRecord) { + b.logEntry("Timestamp: %d", lr.Timestamp()) + b.logEntry("Severity: %s", lr.SeverityText()) + b.logEntry("ShortName: %s", lr.Name()) + b.logEntry("Body: %s", attributeValueToString(lr.Body())) + b.logAttributeMap("Attributes", lr.Attributes()) +} + +func (b *logDataBuffer) logEvents(description string, se pdata.SpanEventSlice) { + if se.Len() == 0 { + return + } + + b.logEntry("%s:", description) + for i := 0; i < se.Len(); i++ { + e := se.At(i) + b.logEntry("SpanEvent #%d", i) + b.logEntry(" -> Name: %s", e.Name()) + b.logEntry(" -> Timestamp: %d", e.Timestamp()) + b.logEntry(" -> DroppedAttributesCount: %d", e.DroppedAttributesCount()) + + if e.Attributes().Len() == 0 { + return + } + b.logEntry(" -> Attributes:") + e.Attributes().ForEach(func(k string, v pdata.AttributeValue) { + b.logEntry(" -> %s: %s(%s)", k, v.Type().String(), attributeValueToString(v)) + }) + } +} + +func (b *logDataBuffer) logLinks(description string, sl pdata.SpanLinkSlice) { + if sl.Len() == 0 { + return + } + + b.logEntry("%s:", description) + + for i := 0; i < sl.Len(); i++ { + l := sl.At(i) + b.logEntry("SpanLink #%d", i) + b.logEntry(" -> Trace ID: %s", l.TraceID().HexString()) + b.logEntry(" -> ID: %s", l.SpanID().HexString()) + b.logEntry(" -> TraceState: %s", l.TraceState()) + b.logEntry(" -> DroppedAttributesCount: %d", l.DroppedAttributesCount()) + if l.Attributes().Len() == 0 { + return + } + b.logEntry(" -> Attributes:") + l.Attributes().ForEach(func(k string, v pdata.AttributeValue) { + b.logEntry(" -> %s: %s(%s)", k, v.Type().String(), attributeValueToString(v)) + }) + } +} + +func attributeValueToString(av pdata.AttributeValue) string { + switch av.Type() { + case pdata.AttributeValueSTRING: + return av.StringVal() + case pdata.AttributeValueBOOL: + return strconv.FormatBool(av.BoolVal()) + case pdata.AttributeValueDOUBLE: + return strconv.FormatFloat(av.DoubleVal(), 'f', -1, 64) + case pdata.AttributeValueINT: + return strconv.FormatInt(av.IntVal(), 10) + case pdata.AttributeValueARRAY: + return attributeValueArrayToString(av.ArrayVal()) + default: + return fmt.Sprintf("", av.Type()) + } +} + +func attributeValueArrayToString(av pdata.AnyValueArray) string { + var b strings.Builder + b.WriteByte('[') + for i := 0; i < av.Len(); i++ { + if i < av.Len()-1 { + fmt.Fprintf(&b, "%s, ", attributeValueToString(av.At(i))) + } else { + b.WriteString(attributeValueToString(av.At(i))) + } + } + + b.WriteByte(']') + return b.String() +} + +type loggingExporter struct { + logger *zap.Logger + debug bool +} + +func (s *loggingExporter) pushTraceData( + _ context.Context, + td pdata.Traces, +) (int, error) { + + s.logger.Info("TracesExporter", zap.Int("#spans", td.SpanCount())) + + if !s.debug { + return 0, nil + } + + buf := logDataBuffer{} + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + buf.logEntry("ResourceSpans #%d", i) + rs := rss.At(i) + buf.logAttributeMap("Resource labels", rs.Resource().Attributes()) + ilss := rs.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + buf.logEntry("InstrumentationLibrarySpans #%d", j) + ils := ilss.At(j) + buf.logInstrumentationLibrary(ils.InstrumentationLibrary()) + + spans := ils.Spans() + for k := 0; k < spans.Len(); k++ { + buf.logEntry("Span #%d", k) + span := spans.At(k) + buf.logAttr("Trace ID", span.TraceID().HexString()) + buf.logAttr("Parent ID", span.ParentSpanID().HexString()) + buf.logAttr("ID", span.SpanID().HexString()) + buf.logAttr("Name", span.Name()) + buf.logAttr("Kind", span.Kind().String()) + buf.logAttr("Start time", span.StartTime().String()) + buf.logAttr("End time", span.EndTime().String()) + + buf.logAttr("Status code", span.Status().Code().String()) + buf.logAttr("Status message", span.Status().Message()) + + buf.logAttributeMap("Attributes", span.Attributes()) + buf.logEvents("Events", span.Events()) + buf.logLinks("Links", span.Links()) + } + } + } + s.logger.Debug(buf.str.String()) + + return 0, nil +} + +func (s *loggingExporter) pushMetricsData( + _ context.Context, + md pdata.Metrics, +) (int, error) { + s.logger.Info("MetricsExporter", zap.Int("#metrics", md.MetricCount())) + + if !s.debug { + return 0, nil + } + + buf := logDataBuffer{} + rms := md.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + buf.logEntry("ResourceMetrics #%d", i) + rm := rms.At(i) + buf.logAttributeMap("Resource labels", rm.Resource().Attributes()) + ilms := rm.InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + buf.logEntry("InstrumentationLibraryMetrics #%d", j) + ilm := ilms.At(j) + buf.logInstrumentationLibrary(ilm.InstrumentationLibrary()) + metrics := ilm.Metrics() + for k := 0; k < metrics.Len(); k++ { + buf.logEntry("Metric #%d", k) + metric := metrics.At(k) + buf.logMetricDescriptor(metric) + buf.logMetricDataPoints(metric) + } + } + } + + s.logger.Debug(buf.str.String()) + + return 0, nil +} + +// newTraceExporter creates an exporter.TracesExporter that just drops the +// received data and logs debugging messages. +func newTraceExporter(config configmodels.Exporter, level string, logger *zap.Logger) (component.TracesExporter, error) { + s := &loggingExporter{ + debug: strings.ToLower(level) == "debug", + logger: logger, + } + + return exporterhelper.NewTraceExporter( + config, + logger, + s.pushTraceData, + // Disable Timeout/RetryOnFailure and SendingQueue + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(exporterhelper.RetrySettings{Enabled: false}), + exporterhelper.WithQueue(exporterhelper.QueueSettings{Enabled: false}), + exporterhelper.WithShutdown(loggerSync(logger)), + ) +} + +// newMetricsExporter creates an exporter.MetricsExporter that just drops the +// received data and logs debugging messages. +func newMetricsExporter(config configmodels.Exporter, level string, logger *zap.Logger) (component.MetricsExporter, error) { + s := &loggingExporter{ + debug: strings.ToLower(level) == "debug", + logger: logger, + } + + return exporterhelper.NewMetricsExporter( + config, + logger, + s.pushMetricsData, + // Disable Timeout/RetryOnFailure and SendingQueue + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(exporterhelper.RetrySettings{Enabled: false}), + exporterhelper.WithQueue(exporterhelper.QueueSettings{Enabled: false}), + exporterhelper.WithShutdown(loggerSync(logger)), + ) +} + +// newLogsExporter creates an exporter.LogsExporter that just drops the +// received data and logs debugging messages. +func newLogsExporter(config configmodels.Exporter, level string, logger *zap.Logger) (component.LogsExporter, error) { + s := &loggingExporter{ + debug: strings.ToLower(level) == "debug", + logger: logger, + } + + return exporterhelper.NewLogsExporter( + config, + logger, + s.pushLogData, + // Disable Timeout/RetryOnFailure and SendingQueue + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(exporterhelper.RetrySettings{Enabled: false}), + exporterhelper.WithQueue(exporterhelper.QueueSettings{Enabled: false}), + exporterhelper.WithShutdown(loggerSync(logger)), + ) +} + +func (s *loggingExporter) pushLogData( + _ context.Context, + ld pdata.Logs, +) (int, error) { + s.logger.Info("LogsExporter", zap.Int("#logs", ld.LogRecordCount())) + + if !s.debug { + return 0, nil + } + + buf := logDataBuffer{} + rls := ld.ResourceLogs() + for i := 0; i < rls.Len(); i++ { + buf.logEntry("ResourceLog #%d", i) + rl := rls.At(i) + buf.logAttributeMap("Resource labels", rl.Resource().Attributes()) + ills := rl.InstrumentationLibraryLogs() + for j := 0; j < ills.Len(); j++ { + buf.logEntry("InstrumentationLibraryLogs #%d", j) + ils := ills.At(j) + buf.logInstrumentationLibrary(ils.InstrumentationLibrary()) + + logs := ils.Logs() + for k := 0; k < logs.Len(); k++ { + buf.logEntry("LogRecord #%d", k) + lr := logs.At(k) + buf.logLogRecord(lr) + } + } + } + + s.logger.Debug(buf.str.String()) + + return 0, nil +} + +func loggerSync(logger *zap.Logger) func(context.Context) error { + return func(context.Context) error { + // Currently Sync() on stdout and stderr return errors on Linux and macOS, + // respectively: + // + // - sync /dev/stdout: invalid argument + // - sync /dev/stdout: inappropriate ioctl for device + // + // Since these are not actionable ignore them. + err := logger.Sync() + if osErr, ok := err.(*os.PathError); ok { + wrappedErr := osErr.Unwrap() + switch wrappedErr { + case syscall.EINVAL, syscall.ENOTSUP, syscall.ENOTTY: + err = nil + } + } + return err + } +} diff --git a/internal/otel_collector/exporter/loggingexporter/logging_exporter_test.go b/internal/otel_collector/exporter/loggingexporter/logging_exporter_test.go new file mode 100644 index 00000000000..0f839264471 --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/logging_exporter_test.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package loggingexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestLoggingTraceExporterNoErrors(t *testing.T) { + lte, err := newTraceExporter(&configmodels.ExporterSettings{}, "Debug", zap.NewNop()) + require.NotNil(t, lte) + assert.NoError(t, err) + + assert.NoError(t, lte.ConsumeTraces(context.Background(), testdata.GenerateTraceDataEmpty())) + assert.NoError(t, lte.ConsumeTraces(context.Background(), testdata.GenerateTraceDataTwoSpansSameResourceOneDifferent())) + + assert.NoError(t, lte.Shutdown(context.Background())) +} + +func TestLoggingMetricsExporterNoErrors(t *testing.T) { + lme, err := newMetricsExporter(&configmodels.ExporterSettings{}, "DEBUG", zap.NewNop()) + require.NotNil(t, lme) + assert.NoError(t, err) + + assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetricsEmpty())) + assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GeneratMetricsAllTypesWithSampleDatapoints())) + assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetricsAllTypesEmptyDataPoint())) + assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetricsMetricTypeInvalid())) + + assert.NoError(t, lme.Shutdown(context.Background())) +} + +func TestLoggingLogsExporterNoErrors(t *testing.T) { + lle, err := newLogsExporter(&configmodels.ExporterSettings{}, "debug", zap.NewNop()) + require.NotNil(t, lle) + assert.NoError(t, err) + + assert.NoError(t, lle.ConsumeLogs(context.Background(), testdata.GenerateLogDataEmpty())) + assert.NoError(t, lle.ConsumeLogs(context.Background(), testdata.GenerateLogDataOneEmptyResourceLogs())) + assert.NoError(t, lle.ConsumeLogs(context.Background(), testdata.GenerateLogDataNoLogRecords())) + assert.NoError(t, lle.ConsumeLogs(context.Background(), testdata.GenerateLogDataOneEmptyLogs())) + + assert.NoError(t, lle.Shutdown(context.Background())) +} + +func TestNestedArraySerializesCorrectly(t *testing.T) { + ava := pdata.NewAttributeValueArray() + av := ava.ArrayVal() + av.Append(pdata.NewAttributeValueString("foo")) + av.Append(pdata.NewAttributeValueInt(42)) + + ava2 := pdata.NewAttributeValueArray() + av2 := ava2.ArrayVal() + av2.Append(pdata.NewAttributeValueString("bar")) + + av.Append(ava2) + + assert.Equal(t, 3, ava.ArrayVal().Len()) + assert.Equal(t, "[foo, 42, [bar]]", attributeValueToString(ava)) +} diff --git a/internal/otel_collector/exporter/loggingexporter/testdata/config.yaml b/internal/otel_collector/exporter/loggingexporter/testdata/config.yaml new file mode 100644 index 00000000000..2d86aabbb1a --- /dev/null +++ b/internal/otel_collector/exporter/loggingexporter/testdata/config.yaml @@ -0,0 +1,22 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + logging: + logging/2: + loglevel: debug + sampling_initial: 10 + sampling_thereafter: 50 + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [logging] + metrics: + receivers: [examplereceiver] + exporters: [logging,logging/2] diff --git a/internal/otel_collector/exporter/opencensusexporter/README.md b/internal/otel_collector/exporter/opencensusexporter/README.md new file mode 100644 index 00000000000..e8fd199a2ad --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/README.md @@ -0,0 +1,47 @@ +# OpenCensus gRPC Exporter + +Exports traces and/or metrics via gRPC using +[OpenCensus](https://opencensus.io/) format. + +Supported pipeline types: traces, metrics + +## Getting Started + +The following settings are required: + +- `endpoint` (no default): host:port to which the exporter is going to send Jaeger trace data, +using the gRPC protocol. The valid syntax is described +[here](https://github.com/grpc/grpc/blob/master/doc/naming.md) + +By default, TLS is enabled: + +- `insecure` (default = `false`): whether to enable client transport security for + the exporter's connection. + +As a result, the following parameters are also required: + +- `cert_file` (no default): path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to false. +- `key_file` (no default): path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to false. + +Example: + +```yaml +exporters: + opencensus: + endpoint: opencensus2:55678 + cert_file: file.cert + key_file: file.key + otlp/2: + endpoint: opencensus2:55678 + insecure: true +``` + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) diff --git a/internal/otel_collector/exporter/opencensusexporter/config.go b/internal/otel_collector/exporter/opencensusexporter/config.go new file mode 100644 index 00000000000..a2df6a41540 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/config.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusexporter + +import ( + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for OpenCensus exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + + configgrpc.GRPCClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + + // The number of workers that send the gRPC requests. + NumWorkers int `mapstructure:"num_workers"` +} diff --git a/internal/otel_collector/exporter/opencensusexporter/config_test.go b/internal/otel_collector/exporter/opencensusexporter/config_test.go new file mode 100644 index 00000000000..ad9f71701e7 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/config_test.go @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusexporter + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["opencensus"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["opencensus/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "opencensus/2", + TypeVal: "opencensus", + }, + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: map[string]string{ + "can you have a . here?": "F0000000-0000-0000-0000-000000000000", + "header1": "234", + "another": "somevalue", + }, + Endpoint: "1.2.3.4:1234", + Compression: "on", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/var/lib/mycert.pem", + }, + Insecure: false, + }, + Keepalive: &configgrpc.KeepaliveClientConfig{ + Time: 20, + PermitWithoutStream: true, + Timeout: 30, + }, + WriteBufferSize: 512 * 1024, + BalancerName: "round_robin", + }, + NumWorkers: 123, + }) +} diff --git a/internal/otel_collector/exporter/opencensusexporter/factory.go b/internal/otel_collector/exporter/opencensusexporter/factory.go new file mode 100644 index 00000000000..aabdd8e6e30 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/factory.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusexporter + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "opencensus" +) + +// NewFactory creates a factory for OTLP exporter. +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter), + exporterhelper.WithMetrics(createMetricsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: map[string]string{}, + // We almost read 0 bytes, so no need to tune ReadBufferSize. + WriteBufferSize: 512 * 1024, + }, + NumWorkers: 2, + } +} + +func createTraceExporter(ctx context.Context, params component.ExporterCreateParams, config configmodels.Exporter) (component.TracesExporter, error) { + oCfg := config.(*Config) + return newTraceExporter(ctx, oCfg, params.Logger) +} + +func createMetricsExporter(ctx context.Context, params component.ExporterCreateParams, config configmodels.Exporter) (component.MetricsExporter, error) { + oCfg := config.(*Config) + return newMetricsExporter(ctx, oCfg, params.Logger) +} diff --git a/internal/otel_collector/exporter/opencensusexporter/factory_test.go b/internal/otel_collector/exporter/opencensusexporter/factory_test.go new file mode 100644 index 00000000000..e243af20865 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/factory_test.go @@ -0,0 +1,180 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusexporter + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/testutil" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateTraceExporter(t *testing.T) { + endpoint := testutil.GetAvailableLocalAddress(t) + tests := []struct { + name string + config Config + mustFail bool + }{ + { + name: "NoEndpoint", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: "", + }, + NumWorkers: 3, + }, + mustFail: true, + }, + { + name: "ZeroNumWorkers", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + }, + NumWorkers: 0, + }, + mustFail: true, + }, + { + name: "UseSecure", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + }, + NumWorkers: 3, + }, + }, + { + name: "Keepalive", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Keepalive: &configgrpc.KeepaliveClientConfig{ + Time: 30 * time.Second, + Timeout: 25 * time.Second, + PermitWithoutStream: true, + }, + }, + NumWorkers: 3, + }, + }, + { + name: "Compression", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Compression: configgrpc.CompressionGzip, + }, + NumWorkers: 3, + }, + }, + { + name: "Headers", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Headers: map[string]string{ + "hdr1": "val1", + "hdr2": "val2", + }, + }, + NumWorkers: 3, + }, + }, + { + name: "CompressionError", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Compression: "unknown compression", + }, + NumWorkers: 3, + }, + mustFail: true, + }, + { + name: "CaCert", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "testdata/test_cert.pem", + }, + }, + }, + NumWorkers: 3, + }, + }, + { + name: "CertPemFileError", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "nosuchfile", + }, + }, + }, + NumWorkers: 3, + }, + mustFail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + params := component.ExporterCreateParams{Logger: zap.NewNop()} + tReceiver, tErr := createTraceExporter(context.Background(), params, &tt.config) + checkErrorsAndShutdown(t, tReceiver, tErr, tt.mustFail) + mReceiver, mErr := createMetricsExporter(context.Background(), params, &tt.config) + checkErrorsAndShutdown(t, mReceiver, mErr, tt.mustFail) + }) + } +} + +func checkErrorsAndShutdown(t *testing.T, receiver component.Receiver, err error, mustFail bool) { + if mustFail { + assert.NotNil(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, receiver) + + require.NoError(t, receiver.Shutdown(context.Background())) + } +} diff --git a/internal/otel_collector/exporter/opencensusexporter/opencensus.go b/internal/otel_collector/exporter/opencensusexporter/opencensus.go new file mode 100644 index 00000000000..42e98e90876 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/opencensus.go @@ -0,0 +1,285 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusexporter + +import ( + "context" + "errors" + "fmt" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/translator/internaldata" +) + +// See https://godoc.org/google.golang.org/grpc#ClientConn.NewStream +// why we need to keep the cancel func to cancel the stream +type tracesClientWithCancel struct { + cancel context.CancelFunc + tsec agenttracepb.TraceService_ExportClient +} + +// See https://godoc.org/google.golang.org/grpc#ClientConn.NewStream +// why we need to keep the cancel func to cancel the stream +type metricsClientWithCancel struct { + cancel context.CancelFunc + msec agentmetricspb.MetricsService_ExportClient +} + +type ocExporter struct { + cfg *Config + // gRPC clients and connection. + traceSvcClient agenttracepb.TraceServiceClient + metricsSvcClient agentmetricspb.MetricsServiceClient + // In any of the channels we keep always NumWorkers object (sometimes nil), + // to make sure we don't open more than NumWorkers RPCs at any moment. + tracesClients chan *tracesClientWithCancel + metricsClients chan *metricsClientWithCancel + grpcClientConn *grpc.ClientConn + metadata metadata.MD +} + +func newOcExporter(ctx context.Context, cfg *Config) (*ocExporter, error) { + if cfg.Endpoint == "" { + return nil, errors.New("OpenCensus exporter cfg requires an Endpoint") + } + + if cfg.NumWorkers <= 0 { + return nil, errors.New("OpenCensus exporter cfg requires at least one worker") + } + + dialOpts, err := cfg.GRPCClientSettings.ToDialOptions() + if err != nil { + return nil, err + } + + var clientConn *grpc.ClientConn + if clientConn, err = grpc.DialContext(ctx, cfg.GRPCClientSettings.Endpoint, dialOpts...); err != nil { + return nil, err + } + + oce := &ocExporter{ + cfg: cfg, + grpcClientConn: clientConn, + metadata: metadata.New(cfg.GRPCClientSettings.Headers), + } + return oce, nil +} + +func (oce *ocExporter) shutdown(context.Context) error { + if oce.tracesClients != nil { + // First remove all the clients from the channel. + for i := 0; i < oce.cfg.NumWorkers; i++ { + <-oce.tracesClients + } + // Now close the channel + close(oce.tracesClients) + } + if oce.metricsClients != nil { + // First remove all the clients from the channel. + for i := 0; i < oce.cfg.NumWorkers; i++ { + <-oce.metricsClients + } + // Now close the channel + close(oce.metricsClients) + } + return oce.grpcClientConn.Close() +} + +func newTraceExporter(ctx context.Context, cfg *Config, logger *zap.Logger) (component.TracesExporter, error) { + oce, err := newOcExporter(ctx, cfg) + if err != nil { + return nil, err + } + oce.traceSvcClient = agenttracepb.NewTraceServiceClient(oce.grpcClientConn) + oce.tracesClients = make(chan *tracesClientWithCancel, cfg.NumWorkers) + // Try to create rpc clients now. + for i := 0; i < cfg.NumWorkers; i++ { + // Populate the channel with NumWorkers nil RPCs to keep the number of workers + // constant in the channel. + oce.tracesClients <- nil + } + + return exporterhelper.NewTraceExporter( + cfg, + logger, + oce.pushTraceData, + exporterhelper.WithShutdown(oce.shutdown)) +} + +func newMetricsExporter(ctx context.Context, cfg *Config, logger *zap.Logger) (component.MetricsExporter, error) { + oce, err := newOcExporter(ctx, cfg) + if err != nil { + return nil, err + } + oce.metricsSvcClient = agentmetricspb.NewMetricsServiceClient(oce.grpcClientConn) + oce.metricsClients = make(chan *metricsClientWithCancel, cfg.NumWorkers) + // Try to create rpc clients now. + for i := 0; i < cfg.NumWorkers; i++ { + // Populate the channel with NumWorkers nil RPCs to keep the number of workers + // constant in the channel. + oce.metricsClients <- nil + } + + return exporterhelper.NewMetricsExporter( + cfg, + logger, + oce.pushMetricsData, + exporterhelper.WithShutdown(oce.shutdown)) +} + +func (oce *ocExporter) pushTraceData(_ context.Context, td pdata.Traces) (int, error) { + // Get first available trace Client. + tClient, ok := <-oce.tracesClients + if !ok { + err := errors.New("failed to push traces, OpenCensus exporter was already stopped") + return td.SpanCount(), err + } + + // In any of the metricsClients channel we keep always NumWorkers object (sometimes nil), + // to make sure we don't open more than NumWorkers RPCs at any moment. + // Here check if the client is nil and create a new one if that is the case. A nil + // object means that an error happened: could not connect, service went down, etc. + if tClient == nil { + var err error + tClient, err = oce.createTraceServiceRPC() + if err != nil { + // Cannot create an RPC, put back nil to keep the number of workers constant. + oce.tracesClients <- nil + return td.SpanCount(), err + } + } + + octds := internaldata.TraceDataToOC(td) + for _, octd := range octds { + // This is a hack because OC protocol expects a Node for the initial message. + node := octd.Node + if node == nil { + node = &commonpb.Node{} + } + resource := octd.Resource + if resource == nil { + resource = &resourcepb.Resource{} + } + req := &agenttracepb.ExportTraceServiceRequest{ + Spans: octd.Spans, + Resource: resource, + Node: node, + } + if err := tClient.tsec.Send(req); err != nil { + // Error received, cancel the context used to create the RPC to free all resources, + // put back nil to keep the number of workers constant. + tClient.cancel() + oce.tracesClients <- nil + return td.SpanCount(), err + } + } + oce.tracesClients <- tClient + return 0, nil +} + +func (oce *ocExporter) pushMetricsData(_ context.Context, md pdata.Metrics) (int, error) { + // Get first available mClient. + mClient, ok := <-oce.metricsClients + if !ok { + err := errors.New("failed to push metrics, OpenCensus exporter was already stopped") + return metricPointCount(md), err + } + + // In any of the metricsClients channel we keep always NumWorkers object (sometimes nil), + // to make sure we don't open more than NumWorkers RPCs at any moment. + // Here check if the client is nil and create a new one if that is the case. A nil + // object means that an error happened: could not connect, service went down, etc. + if mClient == nil { + var err error + mClient, err = oce.createMetricsServiceRPC() + if err != nil { + // Cannot create an RPC, put back nil to keep the number of workers constant. + oce.metricsClients <- nil + return metricPointCount(md), err + } + } + + ocmds := internaldata.MetricsToOC(md) + for _, ocmd := range ocmds { + // This is a hack because OC protocol expects a Node for the initial message. + node := ocmd.Node + if node == nil { + node = &commonpb.Node{} + } + resource := ocmd.Resource + if resource == nil { + resource = &resourcepb.Resource{} + } + req := &agentmetricspb.ExportMetricsServiceRequest{ + Metrics: ocmd.Metrics, + Resource: resource, + Node: node, + } + if err := mClient.msec.Send(req); err != nil { + // Error received, cancel the context used to create the RPC to free all resources, + // put back nil to keep the number of workers constant. + mClient.cancel() + oce.metricsClients <- nil + return metricPointCount(md), err + } + } + oce.metricsClients <- mClient + return 0, nil +} + +func (oce *ocExporter) createTraceServiceRPC() (*tracesClientWithCancel, error) { + // Initiate the trace service by sending over node identifier info. + ctx, cancel := context.WithCancel(context.Background()) + if len(oce.cfg.Headers) > 0 { + ctx = metadata.NewOutgoingContext(ctx, metadata.New(oce.cfg.Headers)) + } + // Cannot use grpc.WaitForReady(cfg.WaitForReady) because will block forever. + traceClient, err := oce.traceSvcClient.Export(ctx) + if err != nil { + cancel() + return nil, fmt.Errorf("TraceServiceClient: %w", err) + } + return &tracesClientWithCancel{cancel: cancel, tsec: traceClient}, nil +} + +func (oce *ocExporter) createMetricsServiceRPC() (*metricsClientWithCancel, error) { + // Initiate the trace service by sending over node identifier info. + ctx, cancel := context.WithCancel(context.Background()) + if len(oce.cfg.Headers) > 0 { + ctx = metadata.NewOutgoingContext(ctx, metadata.New(oce.cfg.Headers)) + } + // Cannot use grpc.WaitForReady(cfg.WaitForReady) because will block forever. + metricsClient, err := oce.metricsSvcClient.Export(ctx) + if err != nil { + cancel() + return nil, fmt.Errorf("MetricsServiceClient: %w", err) + } + return &metricsClientWithCancel{cancel: cancel, msec: metricsClient}, nil +} + +func metricPointCount(md pdata.Metrics) int { + _, pc := md.MetricAndDataPointCount() + return pc +} diff --git a/internal/otel_collector/exporter/opencensusexporter/opencensus_test.go b/internal/otel_collector/exporter/opencensusexporter/opencensus_test.go new file mode 100644 index 00000000000..abfc4770ebf --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/opencensus_test.go @@ -0,0 +1,227 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/receiver/opencensusreceiver" + "go.opentelemetry.io/collector/testutil" +) + +func TestSendTraces(t *testing.T) { + sink := new(consumertest.TracesSink) + rFactory := opencensusreceiver.NewFactory() + rCfg := rFactory.CreateDefaultConfig().(*opencensusreceiver.Config) + endpoint := testutil.GetAvailableLocalAddress(t) + rCfg.GRPCServerSettings.NetAddr.Endpoint = endpoint + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + recv, err := rFactory.CreateTracesReceiver(context.Background(), params, rCfg, sink) + assert.NoError(t, err) + assert.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, recv.Shutdown(context.Background())) + }) + + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + cfg.NumWorkers = 1 + exp, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + host := componenttest.NewNopHost() + require.NoError(t, exp.Start(context.Background(), host)) + t.Cleanup(func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }) + + td := testdata.GenerateTraceDataOneSpan() + assert.NoError(t, exp.ConsumeTraces(context.Background(), td)) + testutil.WaitFor(t, func() bool { + return len(sink.AllTraces()) == 1 + }) + traces := sink.AllTraces() + require.Len(t, traces, 1) + assert.Equal(t, td, traces[0]) + + sink.Reset() + // Sending data no Node. + td.ResourceSpans().At(0).Resource().Attributes().InitEmptyWithCapacity(0) + assert.NoError(t, exp.ConsumeTraces(context.Background(), td)) + testutil.WaitFor(t, func() bool { + return len(sink.AllTraces()) == 1 + }) + traces = sink.AllTraces() + require.Len(t, traces, 1) + assert.Equal(t, td, traces[0]) +} + +func TestSendTraces_NoBackend(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: "localhost:56569", + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + exp, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + host := componenttest.NewNopHost() + require.NoError(t, exp.Start(context.Background(), host)) + t.Cleanup(func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }) + + td := testdata.GenerateTraceDataOneSpan() + for i := 0; i < 10000; i++ { + assert.Error(t, exp.ConsumeTraces(context.Background(), td)) + } +} + +func TestSendTraces_AfterStop(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: "localhost:56569", + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + exp, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + host := componenttest.NewNopHost() + require.NoError(t, exp.Start(context.Background(), host)) + assert.NoError(t, exp.Shutdown(context.Background())) + + td := testdata.GenerateTraceDataOneSpan() + assert.Error(t, exp.ConsumeTraces(context.Background(), td)) +} + +func TestSendMetrics(t *testing.T) { + sink := new(consumertest.MetricsSink) + rFactory := opencensusreceiver.NewFactory() + rCfg := rFactory.CreateDefaultConfig().(*opencensusreceiver.Config) + endpoint := testutil.GetAvailableLocalAddress(t) + rCfg.GRPCServerSettings.NetAddr.Endpoint = endpoint + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + recv, err := rFactory.CreateMetricsReceiver(context.Background(), params, rCfg, sink) + assert.NoError(t, err) + assert.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, recv.Shutdown(context.Background())) + }) + + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + cfg.NumWorkers = 1 + exp, err := factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + host := componenttest.NewNopHost() + require.NoError(t, exp.Start(context.Background(), host)) + t.Cleanup(func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }) + + md := testdata.GenerateMetricsOneMetric() + assert.NoError(t, exp.ConsumeMetrics(context.Background(), md)) + testutil.WaitFor(t, func() bool { + return len(sink.AllMetrics()) == 1 + }) + metrics := sink.AllMetrics() + require.Len(t, metrics, 1) + assert.Equal(t, md, metrics[0]) + + // Sending data no node. + sink.Reset() + md.ResourceMetrics().At(0).Resource().Attributes().InitEmptyWithCapacity(0) + assert.NoError(t, exp.ConsumeMetrics(context.Background(), md)) + testutil.WaitFor(t, func() bool { + return len(sink.AllMetrics()) == 1 + }) + metrics = sink.AllMetrics() + require.Len(t, metrics, 1) + assert.Equal(t, md, metrics[0]) +} + +func TestSendMetrics_NoBackend(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: "localhost:56569", + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + exp, err := factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + host := componenttest.NewNopHost() + require.NoError(t, exp.Start(context.Background(), host)) + t.Cleanup(func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }) + + md := testdata.GenerateMetricsOneMetric() + for i := 0; i < 10000; i++ { + assert.Error(t, exp.ConsumeMetrics(context.Background(), md)) + } +} + +func TestSendMetrics_AfterStop(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: "localhost:56569", + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + exp, err := factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + host := componenttest.NewNopHost() + require.NoError(t, exp.Start(context.Background(), host)) + assert.NoError(t, exp.Shutdown(context.Background())) + + md := testdata.GenerateMetricsOneMetric() + assert.Error(t, exp.ConsumeMetrics(context.Background(), md)) +} diff --git a/internal/otel_collector/exporter/opencensusexporter/testdata/config.yaml b/internal/otel_collector/exporter/opencensusexporter/testdata/config.yaml new file mode 100644 index 00000000000..20b1284dd59 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/testdata/config.yaml @@ -0,0 +1,29 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + opencensus: + opencensus/2: + endpoint: "1.2.3.4:1234" + compression: "on" + num_workers: 123 + ca_file: /var/lib/mycert.pem + headers: + "can you have a . here?": "F0000000-0000-0000-0000-000000000000" + header1: 234 + another: "somevalue" + balancer_name: "round_robin" + keepalive: + time: 20 + timeout: 30 + permit_without_stream: true + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [opencensus] diff --git a/internal/otel_collector/exporter/opencensusexporter/testdata/test_cert.pem b/internal/otel_collector/exporter/opencensusexporter/testdata/test_cert.pem new file mode 100644 index 00000000000..b2e77b89d49 --- /dev/null +++ b/internal/otel_collector/exporter/opencensusexporter/testdata/test_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE6jCCAtICCQDVU4PtqpqADTANBgkqhkiG9w0BAQsFADA3MQswCQYDVQQGEwJV +UzETMBEGA1UECAwKY2FsaWZvcm5pYTETMBEGA1UECgwKb3BlbmNlbnN1czAeFw0x +OTAzMDQxODA3MjZaFw0yMDAzMDMxODA3MjZaMDcxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApjYWxpZm9ybmlhMRMwEQYDVQQKDApvcGVuY2Vuc3VzMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAy9JQiAOMzArcdiS4szbTuzg5yYijSSY6SvGj +XMs4/LEFLxgGmFfyHXxoVQzV26lTu/AiUFlZi4JY2qlkZyPwmmmSg4fmzikpVPiC +Vv9pvSIojs8gs0sHaOt40Q8ym43bNt3Mh8rYrs+XMERi6Ol9//j4LnfePkNU5uEo +qC8KQamckaMR6UEHFNunyOwvNBsipgTPldQUPGVnCsNKk8olYGAXS7DR25bgbPli +4T9VCSElsSPAODmyo+2MEDagVXa1vVYxKyO2k6oeBS0lsvdRqRTmGggcg0B/dk+a +H1CL9ful0cu9P3dQif+hfGay8udPkwDLPEq1+WnjJFut3Pmbk3SqUCas5iWt76kK +eKFh4k8fCy4yiaZxzvSbm9+bEBHAl0ZXd8pjvAsBfCKe6G9SBzE1DK4FjWiiEGCb +5dGsyTKr33q3DekLvT3LF8ZeON/13d9toucX9PqG2HDwMP/Fb4WjQIzOc/H9wIak +pf7u6QBDGUiCMmoDrp1d8RsI1RPbEhoywH0YlLmwgf+cr1dU7vlISf576EsGxFz4 ++/sZjIBvZBHn/x0MH+bs4J8V3vMujfDoRdhL07bK7q/AkEALUxljKEfoWeqiuVzK +F9BVv3xNhiua2kgPVbMNWPrQ5uotkNp8IykJ3QOuQ3p5pzxdGfpLd6f8gmJDmcbi +AI9dWTcCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVVi4t/Sumre+AGTaU7np9dl2 +tpllbES5ixe6m2uezt5wAzYNNyuQ2mMG2XrSkMy5gvBZRT9nRNSmLV8VEcxZihG0 +YHS5soXnLL3Jdlwxp98WTDPvM1ntxcHyEyqrrg9YDfKn4sOrr5vo2yZzoKwtxtc7 +lue9JormVx7GxMi7NwaUtCbnwAIcqJJpFjt1EhmJOxGqTJPgUvTBdeGvRj30c6fk +pqpUdPbZ7RKPEtbLoMoCBujKnErv+H0G6Vp9WyCHN+Mi9uTMsGwH14cmJjmfwGDC +8/WF4LdlawFnf/arIp9YcVwcP91d4ywyvbuuo2M7qdosQ7k4uRZ3tyggLYShS3RW +BMEhMRDz9dM0oKGF+HnaS824BIh6O6Hn82Vt8uCKS7IbEX99/kkN1KcqqQe6Lwjq +tG/lm4K5yf+FJVDivpZ9mYTvqTBjhTaOp6m3HYSNJfS0hLQVvEuBNXd8bHiXkcLp +rmFOYUWsjxV1Qku3U5Rner0UpB2Fuw9nJcXuDgWG0gjwzAZ83y3du1VIZp0Ad8Vv +IYpaucbImGJszMtNXn3l72K1wvQVIhm9eRwYc3QteJzweHaDsbytZEoS/GhTrZIT +wRe5ZGrjJBJngRANRSm1BH8j6PjLem9mzPb2eytwJJA0lLhUk4vYproVvXcx0vow +5F+5VB1YB8/tbWePmpo= +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/otlpexporter/README.md b/internal/otel_collector/exporter/otlpexporter/README.md new file mode 100644 index 00000000000..d3dd6fbc142 --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/README.md @@ -0,0 +1,51 @@ +# OTLP gRPC Exporter + +Exports data via gRPC using [OTLP]( +https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/protocol/otlp.md) +format. By default, this exporter requires TLS and offers queued retry capabilities. + +:warning: OTLP metrics and logs formats are currently marked as "Alpha" and may change in +incompatible way any time. + +Supported pipeline types: traces, metrics + +## Getting Started + +The following settings are required: + +- `endpoint` (no default): host:port to which the exporter is going to send OTLP trace data, +using the gRPC protocol. The valid syntax is described +[here](https://github.com/grpc/grpc/blob/master/doc/naming.md) + +By default, TLS is enabled: + +- `insecure` (default = `false`): whether to enable client transport security for + the exporter's connection. + +As a result, the following parameters are also required: + +- `cert_file` (no default): path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to false. +- `key_file` (no default): path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to false. + +Example: + +```yaml +exporters: + otlp: + endpoint: otelcol2:55680 + cert_file: file.cert + key_file: file.key + otlp/2: + endpoint: otelcol2:55680 + insecure: true +``` + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) diff --git a/internal/otel_collector/exporter/otlpexporter/config.go b/internal/otel_collector/exporter/otlpexporter/config.go new file mode 100644 index 00000000000..c410ddf5689 --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/config.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpexporter + +import ( + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// Config defines configuration for OpenCensus exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.TimeoutSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + configgrpc.GRPCClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. +} diff --git a/internal/otel_collector/exporter/otlpexporter/config_test.go b/internal/otel_collector/exporter/otlpexporter/config_test.go new file mode 100644 index 00000000000..bf3032a09f0 --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/config_test.go @@ -0,0 +1,95 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpexporter + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["otlp"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["otlp/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "otlp/2", + TypeVal: "otlp", + }, + TimeoutSettings: exporterhelper.TimeoutSettings{ + Timeout: 10 * time.Second, + }, + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 10 * time.Second, + MaxInterval: 1 * time.Minute, + MaxElapsedTime: 10 * time.Minute, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 2, + QueueSize: 10, + }, + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: map[string]string{ + "can you have a . here?": "F0000000-0000-0000-0000-000000000000", + "header1": "234", + "another": "somevalue", + }, + Endpoint: "1.2.3.4:1234", + Compression: "on", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/var/lib/mycert.pem", + }, + Insecure: false, + }, + Keepalive: &configgrpc.KeepaliveClientConfig{ + Time: 20 * time.Second, + PermitWithoutStream: true, + Timeout: 30 * time.Second, + }, + WriteBufferSize: 512 * 1024, + PerRPCAuth: &configgrpc.PerRPCAuthConfig{ + AuthType: "bearer", + BearerToken: "some-token", + }, + BalancerName: "round_robin", + }, + }) +} diff --git a/internal/otel_collector/exporter/otlpexporter/factory.go b/internal/otel_collector/exporter/otlpexporter/factory.go new file mode 100644 index 00000000000..5fa88715efd --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/factory.go @@ -0,0 +1,133 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpexporter + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "otlp" +) + +// NewFactory creates a factory for OTLP exporter. +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter), + exporterhelper.WithMetrics(createMetricsExporter), + exporterhelper.WithLogs(createLogsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + TimeoutSettings: exporterhelper.DefaultTimeoutSettings(), + RetrySettings: exporterhelper.DefaultRetrySettings(), + QueueSettings: exporterhelper.DefaultQueueSettings(), + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Headers: map[string]string{}, + // We almost read 0 bytes, so no need to tune ReadBufferSize. + WriteBufferSize: 512 * 1024, + }, + } +} + +func createTraceExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.TracesExporter, error) { + oce, err := newExporter(cfg) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + oexp, err := exporterhelper.NewTraceExporter( + cfg, + params.Logger, + oce.pushTraceData, + exporterhelper.WithTimeout(oCfg.TimeoutSettings), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings), + exporterhelper.WithShutdown(oce.shutdown)) + if err != nil { + return nil, err + } + + return oexp, nil +} + +func createMetricsExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.MetricsExporter, error) { + oce, err := newExporter(cfg) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + oexp, err := exporterhelper.NewMetricsExporter( + cfg, + params.Logger, + oce.pushMetricsData, + exporterhelper.WithTimeout(oCfg.TimeoutSettings), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings), + exporterhelper.WithShutdown(oce.shutdown), + ) + if err != nil { + return nil, err + } + + return oexp, nil +} + +func createLogsExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.LogsExporter, error) { + oce, err := newExporter(cfg) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + oexp, err := exporterhelper.NewLogsExporter( + cfg, + params.Logger, + oce.pushLogData, + exporterhelper.WithTimeout(oCfg.TimeoutSettings), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings), + exporterhelper.WithShutdown(oce.shutdown), + ) + if err != nil { + return nil, err + } + + return oexp, nil +} diff --git a/internal/otel_collector/exporter/otlpexporter/factory_test.go b/internal/otel_collector/exporter/otlpexporter/factory_test.go new file mode 100644 index 00000000000..0659e52b1cd --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/factory_test.go @@ -0,0 +1,198 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpexporter + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/testutil" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) + ocfg, ok := factory.CreateDefaultConfig().(*Config) + assert.True(t, ok) + assert.Equal(t, ocfg.RetrySettings, exporterhelper.DefaultRetrySettings()) + assert.Equal(t, ocfg.QueueSettings, exporterhelper.DefaultQueueSettings()) + assert.Equal(t, ocfg.TimeoutSettings, exporterhelper.DefaultTimeoutSettings()) +} + +func TestCreateMetricsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings.Endpoint = testutil.GetAvailableLocalAddress(t) + + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + oexp, err := factory.CreateMetricsExporter(context.Background(), creationParams, cfg) + require.Nil(t, err) + require.NotNil(t, oexp) +} + +func TestCreateTraceExporter(t *testing.T) { + endpoint := testutil.GetAvailableLocalAddress(t) + + tests := []struct { + name string + config Config + mustFail bool + }{ + { + name: "NoEndpoint", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: "", + }, + }, + mustFail: true, + }, + { + name: "UseSecure", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + }, + }, + }, + { + name: "Keepalive", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Keepalive: &configgrpc.KeepaliveClientConfig{ + Time: 30 * time.Second, + Timeout: 25 * time.Second, + PermitWithoutStream: true, + }, + }, + }, + }, + { + name: "Compression", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Compression: configgrpc.CompressionGzip, + }, + }, + }, + { + name: "Headers", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Headers: map[string]string{ + "hdr1": "val1", + "hdr2": "val2", + }, + }, + }, + }, + { + name: "NumConsumers", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + }, + }, + }, + { + name: "CompressionError", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + Compression: "unknown compression", + }, + }, + mustFail: true, + }, + { + name: "CaCert", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "testdata/test_cert.pem", + }, + }, + }, + }, + }, + { + name: "CertPemFileError", + config: Config{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "nosuchfile", + }, + }, + }, + }, + mustFail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := NewFactory() + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + consumer, err := factory.CreateTracesExporter(context.Background(), creationParams, &tt.config) + + if tt.mustFail { + assert.NotNil(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, consumer) + + err = consumer.Shutdown(context.Background()) + if err != nil { + // Since the endpoint of OTLP exporter doesn't actually exist, + // exporter may already stop because it cannot connect. + assert.Equal(t, err.Error(), "rpc error: code = Canceled desc = grpc: the client connection is closing") + } + } + }) + } +} + +func TestCreateLogsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings.Endpoint = testutil.GetAvailableLocalAddress(t) + + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + oexp, err := factory.CreateLogsExporter(context.Background(), creationParams, cfg) + require.Nil(t, err) + require.NotNil(t, oexp) +} diff --git a/internal/otel_collector/exporter/otlpexporter/otlp.go b/internal/otel_collector/exporter/otlpexporter/otlp.go new file mode 100644 index 00000000000..cf6dec789ee --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/otlp.go @@ -0,0 +1,248 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpexporter + +import ( + "context" + "errors" + "fmt" + "time" + + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/internal" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" +) + +type exporterImp struct { + // Input configuration. + config *Config + w *grpcSender +} + +var ( + errPermanentError = consumererror.Permanent(errors.New("fatal error sending to server")) +) + +// Crete new exporter and start it. The exporter will begin connecting but +// this function may return before the connection is established. +func newExporter(cfg configmodels.Exporter) (*exporterImp, error) { + oCfg := cfg.(*Config) + + if oCfg.Endpoint == "" { + return nil, errors.New("OTLP exporter config requires an Endpoint") + } + + e := &exporterImp{} + e.config = oCfg + w, err := newGrpcSender(oCfg) + if err != nil { + return nil, err + } + e.w = w + return e, nil +} + +func (e *exporterImp) shutdown(context.Context) error { + return e.w.stop() +} + +func (e *exporterImp) pushTraceData(ctx context.Context, td pdata.Traces) (int, error) { + request := &otlptrace.ExportTraceServiceRequest{ + ResourceSpans: pdata.TracesToOtlp(td), + } + err := e.w.exportTrace(ctx, request) + + if err != nil { + return td.SpanCount(), fmt.Errorf("failed to push trace data via OTLP exporter: %w", err) + } + return 0, nil +} + +func (e *exporterImp) pushMetricsData(ctx context.Context, md pdata.Metrics) (int, error) { + request := &otlpmetrics.ExportMetricsServiceRequest{ + ResourceMetrics: pdata.MetricsToOtlp(md), + } + err := e.w.exportMetrics(ctx, request) + + if err != nil { + return md.MetricCount(), fmt.Errorf("failed to push metrics data via OTLP exporter: %w", err) + } + return 0, nil +} + +func (e *exporterImp) pushLogData(ctx context.Context, logs pdata.Logs) (int, error) { + request := &otlplogs.ExportLogsServiceRequest{ + ResourceLogs: internal.LogsToOtlp(logs.InternalRep()), + } + err := e.w.exportLogs(ctx, request) + + if err != nil { + return logs.LogRecordCount(), fmt.Errorf("failed to push log data via OTLP exporter: %w", err) + } + return 0, nil +} + +type grpcSender struct { + // gRPC clients and connection. + traceExporter otlptrace.TraceServiceClient + metricExporter otlpmetrics.MetricsServiceClient + logExporter otlplogs.LogsServiceClient + grpcClientConn *grpc.ClientConn + metadata metadata.MD + waitForReady bool +} + +func newGrpcSender(config *Config) (*grpcSender, error) { + dialOpts, err := config.GRPCClientSettings.ToDialOptions() + if err != nil { + return nil, err + } + + var clientConn *grpc.ClientConn + if clientConn, err = grpc.Dial(config.GRPCClientSettings.Endpoint, dialOpts...); err != nil { + return nil, err + } + + gs := &grpcSender{ + traceExporter: otlptrace.NewTraceServiceClient(clientConn), + metricExporter: otlpmetrics.NewMetricsServiceClient(clientConn), + logExporter: otlplogs.NewLogsServiceClient(clientConn), + grpcClientConn: clientConn, + metadata: metadata.New(config.GRPCClientSettings.Headers), + waitForReady: config.GRPCClientSettings.WaitForReady, + } + return gs, nil +} + +func (gs *grpcSender) stop() error { + return gs.grpcClientConn.Close() +} + +func (gs *grpcSender) exportTrace(ctx context.Context, request *otlptrace.ExportTraceServiceRequest) error { + _, err := gs.traceExporter.Export(gs.enhanceContext(ctx), request, grpc.WaitForReady(gs.waitForReady)) + return processError(err) +} + +func (gs *grpcSender) exportMetrics(ctx context.Context, request *otlpmetrics.ExportMetricsServiceRequest) error { + _, err := gs.metricExporter.Export(gs.enhanceContext(ctx), request, grpc.WaitForReady(gs.waitForReady)) + return processError(err) +} + +func (gs *grpcSender) exportLogs(ctx context.Context, request *otlplogs.ExportLogsServiceRequest) error { + _, err := gs.logExporter.Export(gs.enhanceContext(ctx), request, grpc.WaitForReady(gs.waitForReady)) + return processError(err) +} + +func (gs *grpcSender) enhanceContext(ctx context.Context) context.Context { + if gs.metadata.Len() > 0 { + return metadata.NewOutgoingContext(ctx, gs.metadata) + } + return ctx +} + +// Send a trace or metrics request to the server. "perform" function is expected to make +// the actual gRPC unary call that sends the request. This function implements the +// common OTLP logic around request handling such as retries and throttling. +func processError(err error) error { + if err == nil { + // Request is successful, we are done. + return nil + } + + // We have an error, check gRPC status code. + + st := status.Convert(err) + if st.Code() == codes.OK { + // Not really an error, still success. + return nil + } + + // Now, this is this a real error. + + if !shouldRetry(st.Code()) { + // It is not a retryable error, we should not retry. + return errPermanentError + } + + // Need to retry. + + // Check if server returned throttling information. + throttleDuration := getThrottleDuration(st) + if throttleDuration != 0 { + return exporterhelper.NewThrottleRetry(err, throttleDuration) + } + + return err +} + +func shouldRetry(code codes.Code) bool { + switch code { + case codes.OK: + // Success. This function should not be called for this code, the best we + // can do is tell the caller not to retry. + return false + + case codes.Canceled, + codes.DeadlineExceeded, + codes.PermissionDenied, + codes.Unauthenticated, + codes.ResourceExhausted, + codes.Aborted, + codes.OutOfRange, + codes.Unavailable, + codes.DataLoss: + // These are retryable errors. + return true + + case codes.Unknown, + codes.InvalidArgument, + codes.NotFound, + codes.AlreadyExists, + codes.FailedPrecondition, + codes.Unimplemented, + codes.Internal: + // These are fatal errors, don't retry. + return false + + default: + // Don't retry on unknown codes. + return false + } +} + +func getThrottleDuration(status *status.Status) time.Duration { + // See if throttling information is available. + for _, detail := range status.Details() { + if t, ok := detail.(*errdetails.RetryInfo); ok { + if t.RetryDelay.Seconds > 0 || t.RetryDelay.Nanos > 0 { + // We are throttled. Wait before retrying as requested by the server. + return time.Duration(t.RetryDelay.Seconds)*time.Second + time.Duration(t.RetryDelay.Nanos)*time.Nanosecond + } + return 0 + } + } + return 0 +} diff --git a/internal/otel_collector/exporter/otlpexporter/otlp_test.go b/internal/otel_collector/exporter/otlpexporter/otlp_test.go new file mode 100644 index 00000000000..8859b89288b --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/otlp_test.go @@ -0,0 +1,535 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpexporter + +import ( + "context" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/pdata" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlptraces "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/testutil" +) + +type mockReceiver struct { + srv *grpc.Server + requestCount int32 + totalItems int32 + mux sync.Mutex + metadata metadata.MD +} + +func (r *mockReceiver) GetMetadata() metadata.MD { + r.mux.Lock() + defer r.mux.Unlock() + return r.metadata +} + +type mockTraceReceiver struct { + mockReceiver + lastRequest *otlptraces.ExportTraceServiceRequest +} + +func (r *mockTraceReceiver) Export( + ctx context.Context, + req *otlptraces.ExportTraceServiceRequest, +) (*otlptraces.ExportTraceServiceResponse, error) { + atomic.AddInt32(&r.requestCount, 1) + spanCount := 0 + for _, rs := range req.ResourceSpans { + for _, ils := range rs.InstrumentationLibrarySpans { + spanCount += len(ils.Spans) + } + } + atomic.AddInt32(&r.totalItems, int32(spanCount)) + r.mux.Lock() + defer r.mux.Unlock() + r.lastRequest = req + r.metadata, _ = metadata.FromIncomingContext(ctx) + return &otlptraces.ExportTraceServiceResponse{}, nil +} + +func (r *mockTraceReceiver) GetLastRequest() *otlptraces.ExportTraceServiceRequest { + r.mux.Lock() + defer r.mux.Unlock() + return r.lastRequest +} + +func otlpTraceReceiverOnGRPCServer(ln net.Listener) *mockTraceReceiver { + rcv := &mockTraceReceiver{ + mockReceiver: mockReceiver{ + srv: obsreport.GRPCServerWithObservabilityEnabled(), + }, + } + + // Now run it as a gRPC server + otlptraces.RegisterTraceServiceServer(rcv.srv, rcv) + go func() { + _ = rcv.srv.Serve(ln) + }() + + return rcv +} + +type mockLogsReceiver struct { + mockReceiver + lastRequest *otlplogs.ExportLogsServiceRequest +} + +func (r *mockLogsReceiver) Export( + ctx context.Context, + req *otlplogs.ExportLogsServiceRequest, +) (*otlplogs.ExportLogsServiceResponse, error) { + atomic.AddInt32(&r.requestCount, 1) + recordCount := 0 + for _, rs := range req.ResourceLogs { + for _, il := range rs.InstrumentationLibraryLogs { + recordCount += len(il.Logs) + } + } + atomic.AddInt32(&r.totalItems, int32(recordCount)) + r.mux.Lock() + defer r.mux.Unlock() + r.lastRequest = req + r.metadata, _ = metadata.FromIncomingContext(ctx) + return &otlplogs.ExportLogsServiceResponse{}, nil +} + +func (r *mockLogsReceiver) GetLastRequest() *otlplogs.ExportLogsServiceRequest { + r.mux.Lock() + defer r.mux.Unlock() + return r.lastRequest +} + +func otlpLogsReceiverOnGRPCServer(ln net.Listener) *mockLogsReceiver { + rcv := &mockLogsReceiver{ + mockReceiver: mockReceiver{ + srv: obsreport.GRPCServerWithObservabilityEnabled(), + }, + } + + // Now run it as a gRPC server + otlplogs.RegisterLogsServiceServer(rcv.srv, rcv) + go func() { + _ = rcv.srv.Serve(ln) + }() + + return rcv +} + +type mockMetricsReceiver struct { + mockReceiver + lastRequest *otlpmetrics.ExportMetricsServiceRequest +} + +func (r *mockMetricsReceiver) Export( + ctx context.Context, + req *otlpmetrics.ExportMetricsServiceRequest, +) (*otlpmetrics.ExportMetricsServiceResponse, error) { + atomic.AddInt32(&r.requestCount, 1) + _, recordCount := pdata.MetricsFromOtlp(req.ResourceMetrics).MetricAndDataPointCount() + atomic.AddInt32(&r.totalItems, int32(recordCount)) + r.mux.Lock() + defer r.mux.Unlock() + r.lastRequest = req + r.metadata, _ = metadata.FromIncomingContext(ctx) + return &otlpmetrics.ExportMetricsServiceResponse{}, nil +} + +func (r *mockMetricsReceiver) GetLastRequest() *otlpmetrics.ExportMetricsServiceRequest { + r.mux.Lock() + defer r.mux.Unlock() + return r.lastRequest +} + +func otlpMetricsReceiverOnGRPCServer(ln net.Listener) *mockMetricsReceiver { + rcv := &mockMetricsReceiver{ + mockReceiver: mockReceiver{ + srv: obsreport.GRPCServerWithObservabilityEnabled(), + }, + } + + // Now run it as a gRPC server + otlpmetrics.RegisterMetricsServiceServer(rcv.srv, rcv) + go func() { + _ = rcv.srv.Serve(ln) + }() + + return rcv +} + +func TestSendTraces(t *testing.T) { + // Start an OTLP-compatible receiver. + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + rcv := otlpTraceReceiverOnGRPCServer(ln) + // Also closes the connection. + defer rcv.srv.GracefulStop() + + // Start an OTLP exporter and point to the receiver. + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: ln.Addr().String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + Headers: map[string]string{ + "header": "header-value", + }, + } + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateTracesExporter(context.Background(), creationParams, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + defer func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }() + + host := componenttest.NewNopHost() + + assert.NoError(t, exp.Start(context.Background(), host)) + + // Ensure that initially there is no data in the receiver. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.requestCount)) + + // Send empty trace. + td := testdata.GenerateTraceDataEmpty() + assert.NoError(t, exp.ConsumeTraces(context.Background(), td)) + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 0 + }, "receive a request") + + // Ensure it was received empty. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.totalItems)) + + // A trace with 2 spans. + td = testdata.GenerateTraceDataTwoSpansSameResource() + + expectedOTLPReq := &otlptraces.ExportTraceServiceRequest{ + ResourceSpans: testdata.GenerateTraceOtlpSameResourceTwoSpans(), + } + + err = exp.ConsumeTraces(context.Background(), td) + assert.NoError(t, err) + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 1 + }, "receive a request") + + expectedHeader := []string{"header-value"} + + // Verify received span. + assert.EqualValues(t, 2, atomic.LoadInt32(&rcv.totalItems)) + assert.EqualValues(t, 2, atomic.LoadInt32(&rcv.requestCount)) + assert.EqualValues(t, expectedOTLPReq, rcv.GetLastRequest()) + + require.EqualValues(t, rcv.GetMetadata().Get("header"), expectedHeader) +} + +func TestSendMetrics(t *testing.T) { + // Start an OTLP-compatible receiver. + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + rcv := otlpMetricsReceiverOnGRPCServer(ln) + // Also closes the connection. + defer rcv.srv.GracefulStop() + + // Start an OTLP exporter and point to the receiver. + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: ln.Addr().String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + Headers: map[string]string{ + "header": "header-value", + }, + } + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateMetricsExporter(context.Background(), creationParams, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + defer func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }() + + host := componenttest.NewNopHost() + + assert.NoError(t, exp.Start(context.Background(), host)) + + // Ensure that initially there is no data in the receiver. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.requestCount)) + + // Send empty trace. + md := testdata.GenerateMetricsEmpty() + assert.NoError(t, exp.ConsumeMetrics(context.Background(), md)) + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 0 + }, "receive a request") + + // Ensure it was received empty. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.totalItems)) + + // A trace with 2 spans. + md = testdata.GenerateMetricsTwoMetrics() + + expectedOTLPReq := &otlpmetrics.ExportMetricsServiceRequest{ + ResourceMetrics: testdata.GenerateMetricsOtlpTwoMetrics(), + } + + err = exp.ConsumeMetrics(context.Background(), md) + assert.NoError(t, err) + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 1 + }, "receive a request") + + expectedHeader := []string{"header-value"} + + // Verify received metrics. + assert.EqualValues(t, 2, atomic.LoadInt32(&rcv.requestCount)) + assert.EqualValues(t, 4, atomic.LoadInt32(&rcv.totalItems)) + assert.EqualValues(t, expectedOTLPReq, rcv.GetLastRequest()) + + require.EqualValues(t, rcv.GetMetadata().Get("header"), expectedHeader) +} + +func TestSendTraceDataServerDownAndUp(t *testing.T) { + // Find the addr, but don't start the server. + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + // Start an OTLP exporter and point to the receiver. + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + // Disable queuing to ensure that we execute the request when calling ConsumeTraces + // otherwise we will not see the error. + cfg.QueueSettings.Enabled = false + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: ln.Addr().String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + // Need to wait for every request blocking until either request timeouts or succeed. + // Do not rely on external retry logic here, if that is intended set InitialInterval to 100ms. + WaitForReady: true, + } + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateTracesExporter(context.Background(), creationParams, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + defer func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }() + + host := componenttest.NewNopHost() + + assert.NoError(t, exp.Start(context.Background(), host)) + + // A trace with 2 spans. + td := testdata.GenerateTraceDataTwoSpansSameResource() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + assert.Error(t, exp.ConsumeTraces(ctx, td)) + assert.EqualValues(t, context.DeadlineExceeded, ctx.Err()) + cancel() + + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + assert.Error(t, exp.ConsumeTraces(ctx, td)) + assert.EqualValues(t, context.DeadlineExceeded, ctx.Err()) + cancel() + + startServerAndMakeRequest(t, exp, td, ln) + + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + assert.Error(t, exp.ConsumeTraces(ctx, td)) + assert.EqualValues(t, context.DeadlineExceeded, ctx.Err()) + cancel() + + // First call to startServerAndMakeRequest closed the connection. There is a race condition here that the + // port may be reused, if this gets flaky rethink what to do. + ln, err = net.Listen("tcp", ln.Addr().String()) + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + startServerAndMakeRequest(t, exp, td, ln) + + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + assert.Error(t, exp.ConsumeTraces(ctx, td)) + assert.EqualValues(t, context.DeadlineExceeded, ctx.Err()) + cancel() +} + +func TestSendTraceDataServerStartWhileRequest(t *testing.T) { + // Find the addr, but don't start the server. + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + // Start an OTLP exporter and point to the receiver. + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: ln.Addr().String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateTracesExporter(context.Background(), creationParams, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + defer func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }() + + host := componenttest.NewNopHost() + + assert.NoError(t, exp.Start(context.Background(), host)) + + // A trace with 2 spans. + td := testdata.GenerateTraceDataTwoSpansSameResource() + done := make(chan bool, 1) + defer close(done) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + go func() { + assert.NoError(t, exp.ConsumeTraces(ctx, td)) + done <- true + }() + + time.Sleep(2 * time.Second) + rcv := otlpTraceReceiverOnGRPCServer(ln) + defer rcv.srv.GracefulStop() + // Wait until one of the conditions below triggers. + select { + case <-ctx.Done(): + t.Fail() + case <-done: + assert.NoError(t, ctx.Err()) + } + cancel() +} + +func startServerAndMakeRequest(t *testing.T, exp component.TracesExporter, td pdata.Traces, ln net.Listener) { + rcv := otlpTraceReceiverOnGRPCServer(ln) + defer rcv.srv.GracefulStop() + // Ensure that initially there is no data in the receiver. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.requestCount)) + + // Resend the request, this should succeed. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + assert.NoError(t, exp.ConsumeTraces(ctx, td)) + cancel() + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 0 + }, "receive a request") + + expectedOTLPReq := &otlptraces.ExportTraceServiceRequest{ + ResourceSpans: testdata.GenerateTraceOtlpSameResourceTwoSpans(), + } + + // Verify received span. + assert.EqualValues(t, 2, atomic.LoadInt32(&rcv.totalItems)) + assert.EqualValues(t, expectedOTLPReq, rcv.GetLastRequest()) +} + +func TestSendLogData(t *testing.T) { + // Start an OTLP-compatible receiver. + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + rcv := otlpLogsReceiverOnGRPCServer(ln) + // Also closes the connection. + defer rcv.srv.GracefulStop() + + // Start an OTLP exporter and point to the receiver. + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{ + Endpoint: ln.Addr().String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + } + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateLogsExporter(context.Background(), creationParams, cfg) + require.NoError(t, err) + require.NotNil(t, exp) + defer func() { + assert.NoError(t, exp.Shutdown(context.Background())) + }() + + host := componenttest.NewNopHost() + + assert.NoError(t, exp.Start(context.Background(), host)) + + // Ensure that initially there is no data in the receiver. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.requestCount)) + + // Send empty request. + td := testdata.GenerateLogDataEmpty() + assert.NoError(t, exp.ConsumeLogs(context.Background(), td)) + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 0 + }, "receive a request") + + // Ensure it was received empty. + assert.EqualValues(t, 0, atomic.LoadInt32(&rcv.totalItems)) + + // A request with 2 log entries. + td = testdata.GenerateLogDataTwoLogsSameResource() + + expectedOTLPReq := &otlplogs.ExportLogsServiceRequest{ + ResourceLogs: testdata.GenerateLogOtlpSameResourceTwoLogs(), + } + + err = exp.ConsumeLogs(context.Background(), td) + assert.NoError(t, err) + + // Wait until it is received. + testutil.WaitFor(t, func() bool { + return atomic.LoadInt32(&rcv.requestCount) > 1 + }, "receive a request") + + // Verify received logs. + assert.EqualValues(t, 2, atomic.LoadInt32(&rcv.requestCount)) + assert.EqualValues(t, 2, atomic.LoadInt32(&rcv.totalItems)) + assert.EqualValues(t, expectedOTLPReq, rcv.GetLastRequest()) +} diff --git a/internal/otel_collector/exporter/otlpexporter/testdata/config.yaml b/internal/otel_collector/exporter/otlpexporter/testdata/config.yaml new file mode 100644 index 00000000000..c889a7af635 --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/testdata/config.yaml @@ -0,0 +1,41 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + otlp: + otlp/2: + endpoint: "1.2.3.4:1234" + compression: "on" + ca_file: /var/lib/mycert.pem + timeout: 10s + sending_queue: + enabled: true + num_consumers: 2 + queue_size: 10 + retry_on_failure: + enabled: true + initial_interval: 10s + max_interval: 60s + max_elapsed_time: 10m + per_rpc_auth: + type: bearer + bearer_token: some-token + headers: + "can you have a . here?": "F0000000-0000-0000-0000-000000000000" + header1: 234 + another: "somevalue" + keepalive: + time: 20s + timeout: 30s + permit_without_stream: true + balancer_name: "round_robin" + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [otlp] diff --git a/internal/otel_collector/exporter/otlpexporter/testdata/test_cert.pem b/internal/otel_collector/exporter/otlpexporter/testdata/test_cert.pem new file mode 100644 index 00000000000..b2e77b89d49 --- /dev/null +++ b/internal/otel_collector/exporter/otlpexporter/testdata/test_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE6jCCAtICCQDVU4PtqpqADTANBgkqhkiG9w0BAQsFADA3MQswCQYDVQQGEwJV +UzETMBEGA1UECAwKY2FsaWZvcm5pYTETMBEGA1UECgwKb3BlbmNlbnN1czAeFw0x +OTAzMDQxODA3MjZaFw0yMDAzMDMxODA3MjZaMDcxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApjYWxpZm9ybmlhMRMwEQYDVQQKDApvcGVuY2Vuc3VzMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAy9JQiAOMzArcdiS4szbTuzg5yYijSSY6SvGj +XMs4/LEFLxgGmFfyHXxoVQzV26lTu/AiUFlZi4JY2qlkZyPwmmmSg4fmzikpVPiC +Vv9pvSIojs8gs0sHaOt40Q8ym43bNt3Mh8rYrs+XMERi6Ol9//j4LnfePkNU5uEo +qC8KQamckaMR6UEHFNunyOwvNBsipgTPldQUPGVnCsNKk8olYGAXS7DR25bgbPli +4T9VCSElsSPAODmyo+2MEDagVXa1vVYxKyO2k6oeBS0lsvdRqRTmGggcg0B/dk+a +H1CL9ful0cu9P3dQif+hfGay8udPkwDLPEq1+WnjJFut3Pmbk3SqUCas5iWt76kK +eKFh4k8fCy4yiaZxzvSbm9+bEBHAl0ZXd8pjvAsBfCKe6G9SBzE1DK4FjWiiEGCb +5dGsyTKr33q3DekLvT3LF8ZeON/13d9toucX9PqG2HDwMP/Fb4WjQIzOc/H9wIak +pf7u6QBDGUiCMmoDrp1d8RsI1RPbEhoywH0YlLmwgf+cr1dU7vlISf576EsGxFz4 ++/sZjIBvZBHn/x0MH+bs4J8V3vMujfDoRdhL07bK7q/AkEALUxljKEfoWeqiuVzK +F9BVv3xNhiua2kgPVbMNWPrQ5uotkNp8IykJ3QOuQ3p5pzxdGfpLd6f8gmJDmcbi +AI9dWTcCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVVi4t/Sumre+AGTaU7np9dl2 +tpllbES5ixe6m2uezt5wAzYNNyuQ2mMG2XrSkMy5gvBZRT9nRNSmLV8VEcxZihG0 +YHS5soXnLL3Jdlwxp98WTDPvM1ntxcHyEyqrrg9YDfKn4sOrr5vo2yZzoKwtxtc7 +lue9JormVx7GxMi7NwaUtCbnwAIcqJJpFjt1EhmJOxGqTJPgUvTBdeGvRj30c6fk +pqpUdPbZ7RKPEtbLoMoCBujKnErv+H0G6Vp9WyCHN+Mi9uTMsGwH14cmJjmfwGDC +8/WF4LdlawFnf/arIp9YcVwcP91d4ywyvbuuo2M7qdosQ7k4uRZ3tyggLYShS3RW +BMEhMRDz9dM0oKGF+HnaS824BIh6O6Hn82Vt8uCKS7IbEX99/kkN1KcqqQe6Lwjq +tG/lm4K5yf+FJVDivpZ9mYTvqTBjhTaOp6m3HYSNJfS0hLQVvEuBNXd8bHiXkcLp +rmFOYUWsjxV1Qku3U5Rner0UpB2Fuw9nJcXuDgWG0gjwzAZ83y3du1VIZp0Ad8Vv +IYpaucbImGJszMtNXn3l72K1wvQVIhm9eRwYc3QteJzweHaDsbytZEoS/GhTrZIT +wRe5ZGrjJBJngRANRSm1BH8j6PjLem9mzPb2eytwJJA0lLhUk4vYproVvXcx0vow +5F+5VB1YB8/tbWePmpo= +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/otlphttpexporter/README.md b/internal/otel_collector/exporter/otlphttpexporter/README.md new file mode 100644 index 00000000000..06aa3d4f47b --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/README.md @@ -0,0 +1,50 @@ +# OTLP/HTTP Exporter + +Exports traces and/or metrics via HTTP using [OTLP]( +https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/protocol/otlp.md) +format. + +*Important: OTLP metrics format is currently marked as "Alpha" and may change in +incompatible way any time.* + +The following settings are required: + +- `endpoint` (no default): The target base URL to send data to (e.g.: https://example.com:55681). + To send each signal a corresponding path will be added to this base URL, i.e. for traces + "/v1/traces" will appended, for metrics "/v1/metrics" will be appended, for logs + "/v1/logs" will be appended. + +The following settings can be optionally configured: + +- `traces_endpoint` (no default): The target URL to send trace data to (e.g.: https://example.com:55681/v1/traces). + If this setting is present the the `endpoint` setting is ignored for traces. +- `metrics_endpoint` (no default): The target URL to send metric data to (e.g.: https://example.com:55681/v1/metrics). + If this setting is present the the `endpoint` setting is ignored for metrics. +- `logs_endpoint` (no default): The target URL to send log data to (e.g.: https://example.com:55681/v1/logs). + If this setting is present the the `endpoint` setting is ignored logs. + +- `insecure` (default = false): when set to true disables verifying the server's + certificate chain and host name. The connection is still encrypted but server identity + is not verified. +- `ca_file` path to the CA cert. For a client this verifies the server certificate. Should + only be used if `insecure` is set to false. +- `cert_file` path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to false. +- `key_file` path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to false. + +- `timeout` (default = 30s): HTTP request time limit. For details see https://golang.org/pkg/net/http/#Client +- `read_buffer_size` (default = 0): ReadBufferSize for HTTP client. +- `write_buffer_size` (default = 512 * 1024): WriteBufferSize for HTTP client. + + +Example: + +```yaml +exporters: + otlphttp: + endpoint: https://example.com:55681/v1/traces +``` + +The full list of settings exposed for this exporter are documented [here](./config.go) +with detailed sample configurations [here](./testdata/config.yaml). diff --git a/internal/otel_collector/exporter/otlphttpexporter/config.go b/internal/otel_collector/exporter/otlphttpexporter/config.go new file mode 100644 index 00000000000..6eb8c76efa3 --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/config.go @@ -0,0 +1,38 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlphttpexporter + +import ( + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// Config defines configuration for OTLP/HTTP exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + confighttp.HTTPClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + // The URL to send traces to. If omitted the Endpoint + "/v1/traces" will be used. + TracesEndpoint string `mapstructure:"traces_endpoint"` + + // The URL to send metrics to. If omitted the Endpoint + "/v1/metrics" will be used. + MetricsEndpoint string `mapstructure:"metrics_endpoint"` + + // The URL to send logs to. If omitted the Endpoint + "/v1/logs" will be used. + LogsEndpoint string `mapstructure:"logs_endpoint"` +} diff --git a/internal/otel_collector/exporter/otlphttpexporter/config_test.go b/internal/otel_collector/exporter/otlphttpexporter/config_test.go new file mode 100644 index 00000000000..a0d3aa83a3b --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/config_test.go @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlphttpexporter + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["otlphttp"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["otlphttp/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "otlphttp/2", + TypeVal: "otlphttp", + }, + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 10 * time.Second, + MaxInterval: 1 * time.Minute, + MaxElapsedTime: 10 * time.Minute, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 2, + QueueSize: 10, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Headers: map[string]string{ + "can you have a . here?": "F0000000-0000-0000-0000-000000000000", + "header1": "234", + "another": "somevalue", + }, + Endpoint: "https://1.2.3.4:1234", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/var/lib/mycert.pem", + CertFile: "certfile", + KeyFile: "keyfile", + }, + Insecure: true, + }, + ReadBufferSize: 123, + WriteBufferSize: 345, + Timeout: time.Second * 10, + }, + }) +} diff --git a/internal/otel_collector/exporter/otlphttpexporter/factory.go b/internal/otel_collector/exporter/otlphttpexporter/factory.go new file mode 100644 index 00000000000..592f0e016ab --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/factory.go @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlphttpexporter + +import ( + "context" + "fmt" + "net/url" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "otlphttp" +) + +// NewFactory creates a factory for OTLP exporter. +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter), + exporterhelper.WithMetrics(createMetricsExporter), + exporterhelper.WithLogs(createLogsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + RetrySettings: exporterhelper.DefaultRetrySettings(), + QueueSettings: exporterhelper.DefaultQueueSettings(), + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "", + Timeout: 30 * time.Second, + Headers: map[string]string{}, + // We almost read 0 bytes, so no need to tune ReadBufferSize. + WriteBufferSize: 512 * 1024, + }, + } +} + +func composeSignalURL(oCfg *Config, signalOverrideURL string, signalName string) (string, error) { + switch { + case signalOverrideURL != "": + _, err := url.Parse(signalOverrideURL) + if err != nil { + return "", fmt.Errorf("%s_endpoint must be a valid URL", signalName) + } + return signalOverrideURL, nil + case oCfg.Endpoint == "": + return "", fmt.Errorf("either endpoint or %s_endpoint must be specified", signalName) + default: + return oCfg.Endpoint + "/v1/" + signalName, nil + } +} + +func createTraceExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.TracesExporter, error) { + oce, err := newExporter(cfg, params.Logger) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + + oce.tracesURL, err = composeSignalURL(oCfg, oCfg.TracesEndpoint, "traces") + if err != nil { + return nil, err + } + + return exporterhelper.NewTraceExporter( + cfg, + params.Logger, + oce.pushTraceData, + // explicitly disable since we rely on http.Client timeout logic. + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings)) +} + +func createMetricsExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.MetricsExporter, error) { + oce, err := newExporter(cfg, params.Logger) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + + oce.metricsURL, err = composeSignalURL(oCfg, oCfg.MetricsEndpoint, "metrics") + if err != nil { + return nil, err + } + + return exporterhelper.NewMetricsExporter( + cfg, + params.Logger, + oce.pushMetricsData, + // explicitly disable since we rely on http.Client timeout logic. + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings)) +} + +func createLogsExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.LogsExporter, error) { + oce, err := newExporter(cfg, params.Logger) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + + oce.logsURL, err = composeSignalURL(oCfg, oCfg.LogsEndpoint, "logs") + if err != nil { + return nil, err + } + + return exporterhelper.NewLogsExporter( + cfg, + params.Logger, + oce.pushLogData, + // explicitly disable since we rely on http.Client timeout logic. + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(oCfg.RetrySettings), + exporterhelper.WithQueue(oCfg.QueueSettings)) +} diff --git a/internal/otel_collector/exporter/otlphttpexporter/factory_test.go b/internal/otel_collector/exporter/otlphttpexporter/factory_test.go new file mode 100644 index 00000000000..c9c76a3e2dd --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/factory_test.go @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlphttpexporter + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/testutil" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) + ocfg, ok := factory.CreateDefaultConfig().(*Config) + assert.True(t, ok) + assert.Equal(t, ocfg.HTTPClientSettings.Endpoint, "") + assert.Equal(t, ocfg.HTTPClientSettings.Timeout, 30*time.Second, "default timeout is 30 second") + assert.Equal(t, ocfg.RetrySettings.Enabled, true, "default retry is enabled") + assert.Equal(t, ocfg.RetrySettings.MaxElapsedTime, 300*time.Second, "default retry MaxElapsedTime") + assert.Equal(t, ocfg.RetrySettings.InitialInterval, 5*time.Second, "default retry InitialInterval") + assert.Equal(t, ocfg.RetrySettings.MaxInterval, 30*time.Second, "default retry MaxInterval") + assert.Equal(t, ocfg.QueueSettings.Enabled, true, "default sending queue is enabled") +} + +func TestCreateMetricsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.HTTPClientSettings.Endpoint = "http://" + testutil.GetAvailableLocalAddress(t) + + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + oexp, err := factory.CreateMetricsExporter(context.Background(), creationParams, cfg) + require.Nil(t, err) + require.NotNil(t, oexp) +} + +func TestCreateTraceExporter(t *testing.T) { + endpoint := "http://" + testutil.GetAvailableLocalAddress(t) + + tests := []struct { + name string + config Config + mustFail bool + }{ + { + name: "NoEndpoint", + config: Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "", + }, + }, + mustFail: true, + }, + { + name: "UseSecure", + config: Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + Insecure: false, + }, + }, + }, + }, + { + name: "Headers", + config: Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: endpoint, + Headers: map[string]string{ + "hdr1": "val1", + "hdr2": "val2", + }, + }, + }, + }, + { + name: "CaCert", + config: Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "testdata/test_cert.pem", + }, + }, + }, + }, + }, + { + name: "CertPemFileError", + config: Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: endpoint, + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "nosuchfile", + }, + }, + }, + }, + mustFail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := NewFactory() + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + consumer, err := factory.CreateTracesExporter(context.Background(), creationParams, &tt.config) + + if tt.mustFail { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, consumer) + + err = consumer.Shutdown(context.Background()) + if err != nil { + // Since the endpoint of OTLP exporter doesn't actually exist, + // exporter may already stop because it cannot connect. + assert.Equal(t, err.Error(), "rpc error: code = Canceled desc = grpc: the client connection is closing") + } + } + }) + } +} + +func TestCreateLogsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.HTTPClientSettings.Endpoint = "http://" + testutil.GetAvailableLocalAddress(t) + + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + oexp, err := factory.CreateLogsExporter(context.Background(), creationParams, cfg) + require.Nil(t, err) + require.NotNil(t, oexp) +} diff --git a/internal/otel_collector/exporter/otlphttpexporter/otlp.go b/internal/otel_collector/exporter/otlphttpexporter/otlp.go new file mode 100644 index 00000000000..0b2618f283b --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/otlp.go @@ -0,0 +1,206 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlphttpexporter + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "time" + + "go.uber.org/zap" + "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +type exporterImp struct { + // Input configuration. + config *Config + client *http.Client + tracesURL string + metricsURL string + logsURL string + logger *zap.Logger +} + +const ( + headerRetryAfter = "Retry-After" + maxHTTPResponseReadBytes = 64 * 1024 +) + +// Crete new exporter. +func newExporter(cfg configmodels.Exporter, logger *zap.Logger) (*exporterImp, error) { + oCfg := cfg.(*Config) + + if oCfg.Endpoint != "" { + _, err := url.Parse(oCfg.Endpoint) + if err != nil { + return nil, errors.New("endpoint must be a valid URL") + } + } + + client, err := oCfg.HTTPClientSettings.ToClient() + if err != nil { + return nil, err + } + + return &exporterImp{ + config: oCfg, + client: client, + logger: logger, + }, nil +} + +func (e *exporterImp) pushTraceData(ctx context.Context, traces pdata.Traces) (int, error) { + request, err := traces.ToOtlpProtoBytes() + if err != nil { + return traces.SpanCount(), consumererror.Permanent(err) + } + + err = e.export(ctx, e.tracesURL, request) + if err != nil { + return traces.SpanCount(), err + } + + return 0, nil +} + +func (e *exporterImp) pushMetricsData(ctx context.Context, metrics pdata.Metrics) (int, error) { + request, err := metrics.ToOtlpProtoBytes() + if err != nil { + return metrics.MetricCount(), consumererror.Permanent(err) + } + + err = e.export(ctx, e.metricsURL, request) + if err != nil { + return metrics.MetricCount(), err + } + + return 0, nil +} + +func (e *exporterImp) pushLogData(ctx context.Context, logs pdata.Logs) (int, error) { + request, err := logs.ToOtlpProtoBytes() + if err != nil { + return logs.LogRecordCount(), consumererror.Permanent(err) + } + + err = e.export(ctx, e.logsURL, request) + if err != nil { + return logs.LogRecordCount(), err + } + + return 0, nil +} + +func (e *exporterImp) export(ctx context.Context, url string, request []byte) error { + e.logger.Debug("Preparing to make HTTP request", zap.String("url", url)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(request)) + if err != nil { + return consumererror.Permanent(err) + } + req.Header.Set("Content-Type", "application/x-protobuf") + + resp, err := e.client.Do(req) + if err != nil { + return fmt.Errorf("failed to make an HTTP request: %w", err) + } + + defer func() { + // Discard any remaining response body when we are done reading. + io.CopyN(ioutil.Discard, resp.Body, maxHTTPResponseReadBytes) + resp.Body.Close() + }() + + if resp.StatusCode >= 200 && resp.StatusCode <= 299 { + // Request is successful. + return nil + } + + respStatus := readResponse(resp) + + // Format the error message. Use the status if it is present in the response. + var formattedErr error + if respStatus != nil { + formattedErr = fmt.Errorf( + "error exporting items, request to %s responded with HTTP Status Code %d, Message=%s, Details=%v", + url, resp.StatusCode, respStatus.Message, respStatus.Details) + } else { + formattedErr = fmt.Errorf( + "error exporting items, request to %s responded with HTTP Status Code %d", + url, resp.StatusCode) + } + + // Check if the server is overwhelmed. + // See spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/protocol/otlp.md#throttling-1 + if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable { + // Fallback to 0 if the Retry-After header is not present. This will trigger the + // default backoff policy by our caller (retry handler). + retryAfter := 0 + if val := resp.Header.Get(headerRetryAfter); val != "" { + if seconds, err2 := strconv.Atoi(val); err2 == nil { + retryAfter = seconds + } + } + // Indicate to our caller to pause for the specified number of seconds. + return exporterhelper.NewThrottleRetry(formattedErr, time.Duration(retryAfter)*time.Second) + } + + if resp.StatusCode == http.StatusBadRequest { + // Report the failure as permanent if the server thinks the request is malformed. + return consumererror.Permanent(formattedErr) + } + + // All other errors are retryable, so don't wrap them in consumererror.Permanent(). + return formattedErr +} + +// Read the response and decode the status.Status from the body. +// Returns nil if the response is empty or cannot be decoded. +func readResponse(resp *http.Response) *status.Status { + var respStatus *status.Status + if resp.StatusCode >= 400 && resp.StatusCode <= 599 { + // Request failed. Read the body. OTLP spec says: + // "Response body for all HTTP 4xx and HTTP 5xx responses MUST be a + // Protobuf-encoded Status message that describes the problem." + maxRead := resp.ContentLength + if maxRead == -1 || maxRead > maxHTTPResponseReadBytes { + maxRead = maxHTTPResponseReadBytes + } + respBytes := make([]byte, maxRead) + n, err := io.ReadFull(resp.Body, respBytes) + if err == nil && n > 0 { + // Decode it as Status struct. See https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/protocol/otlp.md#failures + respStatus = &status.Status{} + err = proto.Unmarshal(respBytes, respStatus) + if err != nil { + respStatus = nil + } + } + } + + return respStatus +} diff --git a/internal/otel_collector/exporter/otlphttpexporter/otlp_test.go b/internal/otel_collector/exporter/otlphttpexporter/otlp_test.go new file mode 100644 index 00000000000..f9bc3ce98df --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/otlp_test.go @@ -0,0 +1,416 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlphttpexporter + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/receiver/otlpreceiver" + "go.opentelemetry.io/collector/testutil" +) + +func TestInvalidConfig(t *testing.T) { + config := &Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "", + }, + } + f := NewFactory() + params := component.ExporterCreateParams{Logger: zap.NewNop()} + _, err := f.CreateTracesExporter(context.Background(), params, config) + require.Error(t, err) + _, err = f.CreateMetricsExporter(context.Background(), params, config) + require.Error(t, err) + _, err = f.CreateLogsExporter(context.Background(), params, config) + require.Error(t, err) +} + +func TestTraceNoBackend(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + exp := startTraceExporter(t, "", fmt.Sprintf("http://%s/v1/traces", addr)) + td := testdata.GenerateTraceDataOneSpan() + assert.Error(t, exp.ConsumeTraces(context.Background(), td)) +} + +func TestTraceInvalidUrl(t *testing.T) { + exp := startTraceExporter(t, "http:/\\//this_is_an/*/invalid_url", "") + td := testdata.GenerateTraceDataOneSpan() + assert.Error(t, exp.ConsumeTraces(context.Background(), td)) + + exp = startTraceExporter(t, "", "http:/\\//this_is_an/*/invalid_url") + td = testdata.GenerateTraceDataOneSpan() + assert.Error(t, exp.ConsumeTraces(context.Background(), td)) +} + +func TestTraceError(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + sink := new(consumertest.TracesSink) + sink.SetConsumeError(errors.New("my_error")) + startTraceReceiver(t, addr, sink) + exp := startTraceExporter(t, "", fmt.Sprintf("http://%s/v1/traces", addr)) + + td := testdata.GenerateTraceDataOneSpan() + assert.Error(t, exp.ConsumeTraces(context.Background(), td)) +} + +func TestTraceRoundTrip(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + tests := []struct { + name string + baseURL string + overrideURL string + }{ + { + name: "wrongbase", + baseURL: "http://wronghostname", + overrideURL: fmt.Sprintf("http://%s/v1/traces", addr), + }, + { + name: "onlybase", + baseURL: fmt.Sprintf("http://%s", addr), + overrideURL: "", + }, + { + name: "override", + baseURL: "", + overrideURL: fmt.Sprintf("http://%s/v1/traces", addr), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sink := new(consumertest.TracesSink) + startTraceReceiver(t, addr, sink) + exp := startTraceExporter(t, test.baseURL, test.overrideURL) + + td := testdata.GenerateTraceDataOneSpan() + assert.NoError(t, exp.ConsumeTraces(context.Background(), td)) + require.Eventually(t, func() bool { + return sink.SpansCount() > 0 + }, 1*time.Second, 10*time.Millisecond) + allTraces := sink.AllTraces() + require.Len(t, allTraces, 1) + assert.EqualValues(t, td, allTraces[0]) + }) + } +} + +func TestMetricsError(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + sink := new(consumertest.MetricsSink) + sink.SetConsumeError(errors.New("my_error")) + startMetricsReceiver(t, addr, sink) + exp := startMetricsExporter(t, "", fmt.Sprintf("http://%s/v1/metrics", addr)) + + md := testdata.GenerateMetricsOneMetric() + assert.Error(t, exp.ConsumeMetrics(context.Background(), md)) +} + +func TestMetricsRoundTrip(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + tests := []struct { + name string + baseURL string + overrideURL string + }{ + { + name: "wrongbase", + baseURL: "http://wronghostname", + overrideURL: fmt.Sprintf("http://%s/v1/metrics", addr), + }, + { + name: "onlybase", + baseURL: fmt.Sprintf("http://%s", addr), + overrideURL: "", + }, + { + name: "override", + baseURL: "", + overrideURL: fmt.Sprintf("http://%s/v1/metrics", addr), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sink := new(consumertest.MetricsSink) + startMetricsReceiver(t, addr, sink) + exp := startMetricsExporter(t, test.baseURL, test.overrideURL) + + md := testdata.GenerateMetricsOneMetric() + assert.NoError(t, exp.ConsumeMetrics(context.Background(), md)) + require.Eventually(t, func() bool { + return sink.MetricsCount() > 0 + }, 1*time.Second, 10*time.Millisecond) + allMetrics := sink.AllMetrics() + require.Len(t, allMetrics, 1) + assert.EqualValues(t, md, allMetrics[0]) + }) + } +} + +func TestLogsError(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + sink := new(consumertest.LogsSink) + sink.SetConsumeError(errors.New("my_error")) + startLogsReceiver(t, addr, sink) + exp := startLogsExporter(t, "", fmt.Sprintf("http://%s/v1/logs", addr)) + + md := testdata.GenerateLogDataOneLog() + assert.Error(t, exp.ConsumeLogs(context.Background(), md)) +} + +func TestLogsRoundTrip(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + tests := []struct { + name string + baseURL string + overrideURL string + }{ + { + name: "wrongbase", + baseURL: "http://wronghostname", + overrideURL: fmt.Sprintf("http://%s/v1/logs", addr), + }, + { + name: "onlybase", + baseURL: fmt.Sprintf("http://%s", addr), + overrideURL: "", + }, + { + name: "override", + baseURL: "", + overrideURL: fmt.Sprintf("http://%s/v1/logs", addr), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sink := new(consumertest.LogsSink) + startLogsReceiver(t, addr, sink) + exp := startLogsExporter(t, test.baseURL, test.overrideURL) + + md := testdata.GenerateLogDataOneLog() + assert.NoError(t, exp.ConsumeLogs(context.Background(), md)) + require.Eventually(t, func() bool { + return sink.LogRecordsCount() > 0 + }, 1*time.Second, 10*time.Millisecond) + allLogs := sink.AllLogs() + require.Len(t, allLogs, 1) + assert.EqualValues(t, md, allLogs[0]) + }) + } +} + +func startTraceExporter(t *testing.T, baseURL string, overrideURL string) component.TracesExporter { + factory := NewFactory() + cfg := createExporterConfig(baseURL, factory.CreateDefaultConfig()) + cfg.TracesEndpoint = overrideURL + exp, err := factory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + startAndCleanup(t, exp) + return exp +} + +func startMetricsExporter(t *testing.T, baseURL string, overrideURL string) component.MetricsExporter { + factory := NewFactory() + cfg := createExporterConfig(baseURL, factory.CreateDefaultConfig()) + cfg.MetricsEndpoint = overrideURL + exp, err := factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + startAndCleanup(t, exp) + return exp +} + +func startLogsExporter(t *testing.T, baseURL string, overrideURL string) component.LogsExporter { + factory := NewFactory() + cfg := createExporterConfig(baseURL, factory.CreateDefaultConfig()) + cfg.LogsEndpoint = overrideURL + exp, err := factory.CreateLogsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + startAndCleanup(t, exp) + return exp +} + +func createExporterConfig(baseURL string, defaultCfg configmodels.Exporter) *Config { + cfg := defaultCfg.(*Config) + cfg.Endpoint = baseURL + cfg.QueueSettings.Enabled = false + cfg.RetrySettings.Enabled = false + return cfg +} + +func startTraceReceiver(t *testing.T, addr string, next consumer.TracesConsumer) { + factory := otlpreceiver.NewFactory() + cfg := createReceiverConfig(addr, factory.CreateDefaultConfig()) + recv, err := factory.CreateTracesReceiver(context.Background(), component.ReceiverCreateParams{Logger: zap.NewNop()}, cfg, next) + require.NoError(t, err) + startAndCleanup(t, recv) +} + +func startMetricsReceiver(t *testing.T, addr string, next consumer.MetricsConsumer) { + factory := otlpreceiver.NewFactory() + cfg := createReceiverConfig(addr, factory.CreateDefaultConfig()) + recv, err := factory.CreateMetricsReceiver(context.Background(), component.ReceiverCreateParams{Logger: zap.NewNop()}, cfg, next) + require.NoError(t, err) + startAndCleanup(t, recv) +} + +func startLogsReceiver(t *testing.T, addr string, next consumer.LogsConsumer) { + factory := otlpreceiver.NewFactory() + cfg := createReceiverConfig(addr, factory.CreateDefaultConfig()) + recv, err := factory.CreateLogsReceiver(context.Background(), component.ReceiverCreateParams{Logger: zap.NewNop()}, cfg, next) + require.NoError(t, err) + startAndCleanup(t, recv) +} + +func createReceiverConfig(addr string, defaultCfg configmodels.Exporter) *otlpreceiver.Config { + cfg := defaultCfg.(*otlpreceiver.Config) + cfg.HTTP.Endpoint = addr + cfg.GRPC = nil + return cfg +} + +func startAndCleanup(t *testing.T, cmp component.Component) { + require.NoError(t, cmp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + require.NoError(t, cmp.Shutdown(context.Background())) + }) +} + +func TestErrorResponses(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + errMsgPrefix := fmt.Sprintf("error exporting items, request to http://%s/v1/traces responded with HTTP Status Code ", addr) + + tests := []struct { + name string + responseStatus int + responseBody *status.Status + err error + isPermErr bool + headers map[string]string + }{ + { + name: "400", + responseStatus: http.StatusBadRequest, + responseBody: status.New(codes.InvalidArgument, "Bad field"), + isPermErr: true, + }, + { + name: "404", + responseStatus: http.StatusNotFound, + err: fmt.Errorf(errMsgPrefix + "404"), + }, + { + name: "419", + responseStatus: http.StatusTooManyRequests, + responseBody: status.New(codes.InvalidArgument, "Quota exceeded"), + err: exporterhelper.NewThrottleRetry( + fmt.Errorf(errMsgPrefix+"429, Message=Quota exceeded, Details=[]"), + time.Duration(0)*time.Second), + }, + { + name: "503", + responseStatus: http.StatusServiceUnavailable, + responseBody: status.New(codes.InvalidArgument, "Server overloaded"), + err: exporterhelper.NewThrottleRetry( + fmt.Errorf(errMsgPrefix+"503, Message=Server overloaded, Details=[]"), + time.Duration(0)*time.Second), + }, + { + name: "503-Retry-After", + responseStatus: http.StatusServiceUnavailable, + responseBody: status.New(codes.InvalidArgument, "Server overloaded"), + headers: map[string]string{"Retry-After": "30"}, + err: exporterhelper.NewThrottleRetry( + fmt.Errorf(errMsgPrefix+"503, Message=Server overloaded, Details=[]"), + time.Duration(30)*time.Second), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/v1/traces", func(writer http.ResponseWriter, request *http.Request) { + for k, v := range test.headers { + writer.Header().Add(k, v) + } + writer.WriteHeader(test.responseStatus) + if test.responseBody != nil { + msg, err := proto.Marshal(test.responseBody.Proto()) + require.NoError(t, err) + writer.Write(msg) + } + }) + srv := http.Server{ + Addr: addr, + Handler: mux, + } + ln, err := net.Listen("tcp", addr) + require.NoError(t, err) + go func() { + _ = srv.Serve(ln) + }() + + cfg := &Config{ + TracesEndpoint: fmt.Sprintf("http://%s/v1/traces", addr), + // Create without QueueSettings and RetrySettings so that ConsumeTraces + // returns the errors that we want to check immediately. + } + exp, err := createTraceExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + + traces := pdata.NewTraces() + err = exp.ConsumeTraces(context.Background(), traces) + assert.Error(t, err) + + if test.isPermErr { + assert.True(t, consumererror.IsPermanent(err)) + } else { + assert.EqualValues(t, test.err, err) + } + + srv.Close() + }) + } +} diff --git a/internal/otel_collector/exporter/otlphttpexporter/testdata/config.yaml b/internal/otel_collector/exporter/otlphttpexporter/testdata/config.yaml new file mode 100644 index 00000000000..bf6bb642445 --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/testdata/config.yaml @@ -0,0 +1,37 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + otlphttp: + otlphttp/2: + endpoint: "https://1.2.3.4:1234" + insecure: true + ca_file: /var/lib/mycert.pem + cert_file: certfile + key_file: keyfile + timeout: 10s + read_buffer_size: 123 + write_buffer_size: 345 + sending_queue: + enabled: true + num_consumers: 2 + queue_size: 10 + retry_on_failure: + enabled: true + initial_interval: 10s + max_interval: 60s + max_elapsed_time: 10m + headers: + "can you have a . here?": "F0000000-0000-0000-0000-000000000000" + header1: 234 + another: "somevalue" + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [otlphttp] diff --git a/internal/otel_collector/exporter/otlphttpexporter/testdata/test_cert.pem b/internal/otel_collector/exporter/otlphttpexporter/testdata/test_cert.pem new file mode 100644 index 00000000000..b2e77b89d49 --- /dev/null +++ b/internal/otel_collector/exporter/otlphttpexporter/testdata/test_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE6jCCAtICCQDVU4PtqpqADTANBgkqhkiG9w0BAQsFADA3MQswCQYDVQQGEwJV +UzETMBEGA1UECAwKY2FsaWZvcm5pYTETMBEGA1UECgwKb3BlbmNlbnN1czAeFw0x +OTAzMDQxODA3MjZaFw0yMDAzMDMxODA3MjZaMDcxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApjYWxpZm9ybmlhMRMwEQYDVQQKDApvcGVuY2Vuc3VzMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAy9JQiAOMzArcdiS4szbTuzg5yYijSSY6SvGj +XMs4/LEFLxgGmFfyHXxoVQzV26lTu/AiUFlZi4JY2qlkZyPwmmmSg4fmzikpVPiC +Vv9pvSIojs8gs0sHaOt40Q8ym43bNt3Mh8rYrs+XMERi6Ol9//j4LnfePkNU5uEo +qC8KQamckaMR6UEHFNunyOwvNBsipgTPldQUPGVnCsNKk8olYGAXS7DR25bgbPli +4T9VCSElsSPAODmyo+2MEDagVXa1vVYxKyO2k6oeBS0lsvdRqRTmGggcg0B/dk+a +H1CL9ful0cu9P3dQif+hfGay8udPkwDLPEq1+WnjJFut3Pmbk3SqUCas5iWt76kK +eKFh4k8fCy4yiaZxzvSbm9+bEBHAl0ZXd8pjvAsBfCKe6G9SBzE1DK4FjWiiEGCb +5dGsyTKr33q3DekLvT3LF8ZeON/13d9toucX9PqG2HDwMP/Fb4WjQIzOc/H9wIak +pf7u6QBDGUiCMmoDrp1d8RsI1RPbEhoywH0YlLmwgf+cr1dU7vlISf576EsGxFz4 ++/sZjIBvZBHn/x0MH+bs4J8V3vMujfDoRdhL07bK7q/AkEALUxljKEfoWeqiuVzK +F9BVv3xNhiua2kgPVbMNWPrQ5uotkNp8IykJ3QOuQ3p5pzxdGfpLd6f8gmJDmcbi +AI9dWTcCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVVi4t/Sumre+AGTaU7np9dl2 +tpllbES5ixe6m2uezt5wAzYNNyuQ2mMG2XrSkMy5gvBZRT9nRNSmLV8VEcxZihG0 +YHS5soXnLL3Jdlwxp98WTDPvM1ntxcHyEyqrrg9YDfKn4sOrr5vo2yZzoKwtxtc7 +lue9JormVx7GxMi7NwaUtCbnwAIcqJJpFjt1EhmJOxGqTJPgUvTBdeGvRj30c6fk +pqpUdPbZ7RKPEtbLoMoCBujKnErv+H0G6Vp9WyCHN+Mi9uTMsGwH14cmJjmfwGDC +8/WF4LdlawFnf/arIp9YcVwcP91d4ywyvbuuo2M7qdosQ7k4uRZ3tyggLYShS3RW +BMEhMRDz9dM0oKGF+HnaS824BIh6O6Hn82Vt8uCKS7IbEX99/kkN1KcqqQe6Lwjq +tG/lm4K5yf+FJVDivpZ9mYTvqTBjhTaOp6m3HYSNJfS0hLQVvEuBNXd8bHiXkcLp +rmFOYUWsjxV1Qku3U5Rner0UpB2Fuw9nJcXuDgWG0gjwzAZ83y3du1VIZp0Ad8Vv +IYpaucbImGJszMtNXn3l72K1wvQVIhm9eRwYc3QteJzweHaDsbytZEoS/GhTrZIT +wRe5ZGrjJBJngRANRSm1BH8j6PjLem9mzPb2eytwJJA0lLhUk4vYproVvXcx0vow +5F+5VB1YB8/tbWePmpo= +-----END CERTIFICATE----- diff --git a/internal/otel_collector/exporter/prometheusexporter/README.md b/internal/otel_collector/exporter/prometheusexporter/README.md new file mode 100644 index 00000000000..5f77e711825 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/README.md @@ -0,0 +1,31 @@ +# Prometheus Exporter + +Exports data to a [Prometheus](https://prometheus.io/) back-end. + +Supported pipeline types: metrics + +## Getting Started + +The following settings are required: + +- `endpoint` (no default): Where to send metric data + +The following settings can be optionally configured: + +- `constlabels` (no default): key/values that are applied for every exported metric. +- `namespace` (no default): if set, exports metrics under the provided value. +- `send_timestamps` (default = `false`): if true, sends the timestamp of the underlying + metric sample in the response. + +Example: + +```yaml +exporters: + prometheus: + endpoint: "1.2.3.4:1234" + namespace: test-space + const_labels: + label1: value1 + "another label": spaced value + send_timestamps: true +``` diff --git a/internal/otel_collector/exporter/prometheusexporter/config.go b/internal/otel_collector/exporter/prometheusexporter/config.go new file mode 100644 index 00000000000..e02185d35b5 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/config.go @@ -0,0 +1,38 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusexporter + +import ( + "github.com/prometheus/client_golang/prometheus" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for Prometheus exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + + // The address on which the Prometheus scrape handler will be run on. + Endpoint string `mapstructure:"endpoint"` + + // Namespace if set, exports metrics under the provided value. + Namespace string `mapstructure:"namespace"` + + // ConstLabels are values that are applied for every exported metric. + ConstLabels prometheus.Labels `mapstructure:"const_labels"` + + // SendTimestamps will send the underlying scrape timestamp with the export + SendTimestamps bool `mapstructure:"send_timestamps"` +} diff --git a/internal/otel_collector/exporter/prometheusexporter/config_test.go b/internal/otel_collector/exporter/prometheusexporter/config_test.go new file mode 100644 index 00000000000..db7ae836b41 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/config_test.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusexporter + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["prometheus"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + e1 := cfg.Exporters["prometheus/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "prometheus/2", + TypeVal: "prometheus", + }, + Endpoint: "1.2.3.4:1234", + Namespace: "test-space", + ConstLabels: map[string]string{ + "label1": "value1", + "another label": "spaced value", + }, + SendTimestamps: true, + }) +} diff --git a/internal/otel_collector/exporter/prometheusexporter/factory.go b/internal/otel_collector/exporter/prometheusexporter/factory.go new file mode 100644 index 00000000000..90f5c197c26 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/factory.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusexporter + +import ( + "context" + "net" + "net/http" + "strings" + + "github.com/orijtech/prometheus-go-metrics-exporter" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "prometheus" +) + +// NewFactory creates a factory for OTLP exporter. +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithMetrics(createMetricsExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + ConstLabels: map[string]string{}, + SendTimestamps: false, + } +} + +func createMetricsExporter( + _ context.Context, + _ component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.MetricsExporter, error) { + pcfg := cfg.(*Config) + + addr := strings.TrimSpace(pcfg.Endpoint) + if addr == "" { + return nil, errBlankPrometheusAddress + } + + opts := prometheus.Options{ + Namespace: pcfg.Namespace, + ConstLabels: pcfg.ConstLabels, + SendTimestamps: pcfg.SendTimestamps, + } + pe, err := prometheus.New(opts) + if err != nil { + return nil, err + } + + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + // The Prometheus metrics exporter has to run on the provided address + // as a server that'll be scraped by Prometheus. + mux := http.NewServeMux() + mux.Handle("/metrics", pe) + + srv := &http.Server{Handler: mux} + go func() { + _ = srv.Serve(ln) + }() + + pexp := &prometheusExporter{ + name: cfg.Name(), + exporter: pe, + shutdownFunc: ln.Close, + } + + return pexp, nil +} diff --git a/internal/otel_collector/exporter/prometheusexporter/factory_test.go b/internal/otel_collector/exporter/prometheusexporter/factory_test.go new file mode 100644 index 00000000000..22b2fb988fb --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/factory_test.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateMetricsExporter(t *testing.T) { + cfg := createDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Endpoint = "" + exp, err := createMetricsExporter( + context.Background(), + component.ExporterCreateParams{Logger: zap.NewNop()}, + cfg) + require.Equal(t, errBlankPrometheusAddress, err) + require.Nil(t, exp) +} diff --git a/internal/otel_collector/exporter/prometheusexporter/prometheus.go b/internal/otel_collector/exporter/prometheusexporter/prometheus.go new file mode 100644 index 00000000000..94e3a82bce9 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/prometheus.go @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusexporter + +import ( + "bytes" + "context" + "errors" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + // TODO: once this repository has been transferred to the + // official census-ecosystem location, update this import path. + "github.com/orijtech/prometheus-go-metrics-exporter" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/internaldata" +) + +var errBlankPrometheusAddress = errors.New("expecting a non-blank address to run the Prometheus metrics handler") + +type prometheusExporter struct { + name string + exporter *prometheus.Exporter + shutdownFunc func() error +} + +func (pe *prometheusExporter) Start(_ context.Context, _ component.Host) error { + return nil +} + +func (pe *prometheusExporter) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + ocmds := internaldata.MetricsToOC(md) + for _, ocmd := range ocmds { + merged := make(map[string]*metricspb.Metric) + for _, metric := range ocmd.Metrics { + merge(merged, metric) + } + for _, metric := range merged { + _ = pe.exporter.ExportMetric(ctx, ocmd.Node, ocmd.Resource, metric) + } + } + return nil +} + +// The underlying exporter overwrites timeseries when there are conflicting metric signatures. +// Therefore, we need to merge timeseries that share a metric signature into a single metric before sending. +func merge(m map[string]*metricspb.Metric, metric *metricspb.Metric) { + key := metricSignature(metric) + current, ok := m[key] + if !ok { + m[key] = metric + return + } + current.Timeseries = append(current.Timeseries, metric.Timeseries...) +} + +// Unique identifier of a given promtheus metric +// Assumes label keys are always in the same order +func metricSignature(metric *metricspb.Metric) string { + var buf bytes.Buffer + buf.WriteString(metric.GetMetricDescriptor().GetName()) + labelKeys := metric.GetMetricDescriptor().GetLabelKeys() + for _, labelKey := range labelKeys { + buf.WriteString("-" + labelKey.Key) + } + return buf.String() +} + +// Shutdown stops the exporter and is invoked during shutdown. +func (pe *prometheusExporter) Shutdown(context.Context) error { + return pe.shutdownFunc() +} diff --git a/internal/otel_collector/exporter/prometheusexporter/prometheus_test.go b/internal/otel_collector/exporter/prometheusexporter/prometheus_test.go new file mode 100644 index 00000000000..5814c83e4f7 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/prometheus_test.go @@ -0,0 +1,248 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusexporter + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/translator/internaldata" +) + +func TestPrometheusExporter(t *testing.T) { + tests := []struct { + config *Config + wantErr string + }{ + { + config: &Config{ + Namespace: "test", + ConstLabels: map[string]string{ + "foo0": "bar0", + "code0": "one0", + }, + Endpoint: ":8999", + SendTimestamps: false, + }, + }, + { + config: &Config{}, + wantErr: "expecting a non-blank address to run the Prometheus metrics handler", + }, + } + + factory := NewFactory() + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + for _, tt := range tests { + // Run it a few times to ensure that shutdowns exit cleanly. + for j := 0; j < 3; j++ { + exp, err := factory.CreateMetricsExporter(context.Background(), creationParams, tt.config) + + if tt.wantErr != "" { + require.Error(t, err) + assert.Equal(t, tt.wantErr, err.Error()) + continue + } else { + require.NoError(t, err) + } + + assert.NotNil(t, exp) + require.Nil(t, err) + require.NoError(t, exp.Shutdown(context.Background())) + } + } +} + +func TestPrometheusExporter_endToEnd(t *testing.T) { + config := &Config{ + Namespace: "test", + ConstLabels: map[string]string{ + "foo1": "bar1", + "code1": "one1", + }, + Endpoint: ":7777", + } + + factory := NewFactory() + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateMetricsExporter(context.Background(), creationParams, config) + assert.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, exp.Shutdown(context.Background())) + // trigger a get so that the server cleans up our keepalive socket + http.Get("http://localhost:7777/metrics") + }) + + assert.NotNil(t, exp) + + for delta := 0; delta <= 20; delta += 10 { + md := internaldata.OCToMetrics(consumerdata.MetricsData{Metrics: metricBuilder(int64(delta))}) + assert.NoError(t, exp.ConsumeMetrics(context.Background(), md)) + + res, err := http.Get("http://localhost:7777/metrics") + require.NoError(t, err, "Failed to perform a scrape") + + if g, w := res.StatusCode, 200; g != w { + t.Errorf("Mismatched HTTP response status code: Got: %d Want: %d", g, w) + } + blob, _ := ioutil.ReadAll(res.Body) + _ = res.Body.Close() + want := []string{ + `# HELP test_this_one_there_where_ Extra ones`, + `# TYPE test_this_one_there_where_ counter`, + fmt.Sprintf(`test_this_one_there_where_{arch="x86",code1="one1",foo1="bar1",os="windows"} %v`, 99+delta), + fmt.Sprintf(`test_this_one_there_where_{arch="x86",code1="one1",foo1="bar1",os="linux"} %v`, 100+delta), + } + + for _, w := range want { + if !strings.Contains(string(blob), w) { + t.Errorf("Missing %v from response:\n%v", w, string(blob)) + } + } + } +} + +func TestPrometheusExporter_endToEndWithTimestamps(t *testing.T) { + config := &Config{ + Namespace: "test", + ConstLabels: map[string]string{ + "foo2": "bar2", + "code2": "one2", + }, + Endpoint: ":7777", + SendTimestamps: true, + } + + factory := NewFactory() + creationParams := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := factory.CreateMetricsExporter(context.Background(), creationParams, config) + assert.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, exp.Shutdown(context.Background())) + // trigger a get so that the server cleans up our keepalive socket + http.Get("http://localhost:7777/metrics") + }) + + assert.NotNil(t, exp) + + for delta := 0; delta <= 20; delta += 10 { + md := internaldata.OCToMetrics(consumerdata.MetricsData{Metrics: metricBuilder(int64(delta))}) + assert.NoError(t, exp.ConsumeMetrics(context.Background(), md)) + + res, err := http.Get("http://localhost:7777/metrics") + require.NoError(t, err, "Failed to perform a scrape") + + if g, w := res.StatusCode, 200; g != w { + t.Errorf("Mismatched HTTP response status code: Got: %d Want: %d", g, w) + } + blob, _ := ioutil.ReadAll(res.Body) + _ = res.Body.Close() + want := []string{ + `# HELP test_this_one_there_where_ Extra ones`, + `# TYPE test_this_one_there_where_ counter`, + fmt.Sprintf(`test_this_one_there_where_{arch="x86",code2="one2",foo2="bar2",os="windows"} %v %v`, 99+delta, 1543160298100), + fmt.Sprintf(`test_this_one_there_where_{arch="x86",code2="one2",foo2="bar2",os="linux"} %v %v`, 100+delta, 1543160298100), + } + + for _, w := range want { + if !strings.Contains(string(blob), w) { + t.Errorf("Missing %v from response:\n%v", w, string(blob)) + } + } + } +} + +func metricBuilder(delta int64) []*metricspb.Metric { + return []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "this/one/there(where)", + Description: "Extra ones", + Unit: "1", + Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, + LabelKeys: []*metricspb.LabelKey{{Key: "os"}, {Key: "arch"}}, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ×tamppb.Timestamp{ + Seconds: 1543160298, + Nanos: 100000090, + }, + LabelValues: []*metricspb.LabelValue{ + {Value: "windows", HasValue: true}, + {Value: "x86", HasValue: true}, + }, + Points: []*metricspb.Point{ + { + Timestamp: ×tamppb.Timestamp{ + Seconds: 1543160298, + Nanos: 100000997, + }, + Value: &metricspb.Point_Int64Value{ + Int64Value: 99 + delta, + }, + }, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "this/one/there(where)", + Description: "Extra ones", + Unit: "1", + Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, + LabelKeys: []*metricspb.LabelKey{{Key: "os"}, {Key: "arch"}}, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ×tamppb.Timestamp{ + Seconds: 1543160298, + Nanos: 100000090, + }, + LabelValues: []*metricspb.LabelValue{ + {Value: "linux", HasValue: true}, + {Value: "x86", HasValue: true}, + }, + Points: []*metricspb.Point{ + { + Timestamp: ×tamppb.Timestamp{ + Seconds: 1543160298, + Nanos: 100000997, + }, + Value: &metricspb.Point_Int64Value{ + Int64Value: 100 + delta, + }, + }, + }, + }, + }, + }, + } +} diff --git a/internal/otel_collector/exporter/prometheusexporter/testdata/config.yaml b/internal/otel_collector/exporter/prometheusexporter/testdata/config.yaml new file mode 100644 index 00000000000..8908e9e5a20 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusexporter/testdata/config.yaml @@ -0,0 +1,22 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + prometheus: + prometheus/2: + endpoint: "1.2.3.4:1234" + namespace: test-space + const_labels: + label1: value1 + "another label": spaced value + send_timestamps: true + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [prometheus] diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/DESIGN.md b/internal/otel_collector/exporter/prometheusremotewriteexporter/DESIGN.md new file mode 100644 index 00000000000..67e8ee46166 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/DESIGN.md @@ -0,0 +1,288 @@ + + +# **OpenTelemetry Collector Prometheus Remote Write/Cortex Exporter Design** + +Authors: @huyan0, @danielbang907 + +Date: July 30, 2020 + +## **1. Introduction** + +Prometheus can be integrated with remote storage systems that supports its remote write API. Existing remote storage integration support is included in [Cortex](https://cortexmetrics.io/docs/api/), [influxDB](https://docs.influxdata.com/influxdb/v1.8/supported_protocols/prometheus/), and many [others](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage). + +The following diagram shows an example of Prometheus remote write API usage, with Cortex,n open source, horizontally scalable, highly available, multi-tenant, long term storage, as a remote storage backend. + +![Cortex Archietecture](./img/cortex.png) + +Our project is focused on developing an exporter for the OpenTelemetry Collector to any Prometheus remote storage backend. + +### **1.1 Remote Write API** + +The Prometheus remote write/Cortex exporter should write metrics to a remote URL in a snappy-compressed, [protocol buffer](https://github.com/prometheus/prometheus/blob/master/prompb/remote.proto#L22) encoded HTTP request defined by the Prometheus remote write API. Each request should encode multiple Prometheus remote write TimeSeries, which are composed of a set of labels and a collection of samples. Each label contains a name-value pair of strings, and each sample contains a timestamp-value number pair. + +![Image of TimeSeries](./img/timeseries.png) + +TimeSeries stores its metric name in its labels and does not describe metric types or start timestamps. To convert to TimeSeries data, buckets of a Histogram are broken down into individual TimeSeries with a bound label(`le`), and a similar process happens with quantiles in a Summary. + + +More details of Prometheus remote write API can be found in Prometheus [documentation](https://prometheus.io/docs/prometheus/latest/storage/#overview) and Cortex [documentation](https://cortexmetrics.io/docs/api/). + +### **1.2 Gaps and Assumptions** + +**Gap 1:** +Currently, metrics from the OpenTelemetry SDKs cannot be exported to Prometheus from the collector correctly ([#1255](https://github.com/open-telemetry/opentelemetry-collector/issues/1255)). This is because the SDKs send metrics to the collector via their OTLP exporter, which exports the delta value of cumulative counters. The same issue will arise for exporting to any Prometheus remote storage backend. + +To overcome this gap in the Collector pipeline, we had proposed 2 different solutions: + +1. Add a [metric aggregation processor](https://github.com/open-telemetry/opentelemetry-collector/issues/1422) to the collector pipeline to aggregate delta values into cumulative values for cumulative backends. This solution requires users to set up a collector agent next to each SDK to make sure delta values are aggregated correctly. +2. Require the OTLP exporters in SDKs to [send cumulative values for cumulative metric types to the Collector by default](https://github.com/open-telemetry/opentelemetry-specification/issues/731). Therefore, no aggregation of delta metric values is required in the Collector pipeline for Prometheus/storage backends to properly process the data. + +**Gap 2:** +Another gap is that OTLP metric definition is still in development. This exporter will require refactoring as OTLP changes in the future. + +**Assumptions:** +Because of the gaps mentioned above, this project will convert from the current OTLP metrics and work under the assumption one of the above solutions will be implemented, and all incoming monotonic scalars/histogram/summary metrics should be cumulative or otherwise dropped. More details on the behavior of the exporter is in section 2.2. + +## **2. Prometheus Remote Write/Cortex Exporter** + +The Prometheus remote write/Cortex exporter should receive OTLP metrics, group data points by metric name and label set, convert each group to a TimeSeries, and send all TimeSeries to a storage backend via HTTP. + +### **2.1 Receiving Metrics** +The Prometheus remote write/Cortex exporter receives a MetricsData instance in its PushMetrics() function. MetricsData contains a collection of Metric instances. Each Metric instance contains a series of data points, and each data point has a set of labels associated with it. Since Prometheus remote write TimeSeries are identified by unique sets of labels, the exporter needs to group data points within each Metric instance by their label set, and convert each group to a TimeSeries. + +To group data points by label set, the exporter should create a map with each PushMetrics() call. The key of the map should represent a combination of the following information: + +* the metric type +* the metric name +* the set of labels that identify a unique TimeSeries + + +The exporter should create a signature string as map key by concatenating metric type, metric name, and label names and label values at each data point. To ensure correctness, the label set at each data point should be sorted by label key before generating the signature string. + +An alternative key type is in the exiting label.Set implementation from the OpenTelemetry Go API. It provides a Distinct type that guarantees the result will equal the equivalent Distinct value of any label set with the same elements as this, where sets are made unique by choosing the last value in the input for any given key. If we allocate a Go API's kv.KeyValue for every label of a data point, then a label.Set from the API can be created, and its Distinct value can be used as map key. + + +The value of the map should be Prometheus TimeSeries, and each data point’s value and timestamp should be inserted to its corresponding TimeSeries in the map as a Sample, each metric’s label set and metric name should be combined and translated to a Prometheus label set; a new TimeSeries should be created if the string signature is not in the map. + + +Pseudocode: + + func PushMetrics(metricsData) { + + // Create a map that stores distinct TimeSeries + map := make(map[String][]TimeSeries) + + for metric in metricsData: + for point in metric: + // Generate signature string + sig := pointSignature(metric, point) + + // Find corresponding TimeSeries in map + // Add to TimeSeries + + // Sends TimeSeries to backend + export(map) + } + +### **2.2 Mapping of OTLP Metrics to TimeSeries** + +Each Prometheus remote write TimeSeries represents less semantic information than an OTLP metric. The temporality property of a OTLP metric is ignored in a TimeSeries because it is always considered as cumulative for monotonic types and histogram, and the type property of a OTLP metric is translated by mapping each metric to one or multiple TimeSeries. The following sections explain how to map each OTLP metric type to Prometheus remote write TimeSeries. + + +**INT64, MONOTONIC_INT64, DOUBLE, MONOTONIC_DOUBLE** + +Each unique label set within metrics of these types can be converted to exactly one TimeSeries. From the perspective of Prometheus client types, INT64 and DOUBLE correspond to gauge metrics, and MONOTONIC types correspond to counter metrics. In both cases, data points will be exported directly without aggregation. Any metric of the monotonic types that is not cumulative should be dropped; non-monotonic scalar types are assumed to represent gauge values, thus its temporality is not checked. Monotonic types need to have a `_total` suffix in its metric name when exporting; this is a requirement of Prometheus. + + +**HISTOGRAM** + +Each histogram data point can be converted to 2 + n + 1 Prometheus remote write TimeSeries: + +* 1 *TimeSeries* representing metric_name_count contains HistogramDataPoint.count +* 1 *TimeSeries* representing metric_name_sum contains HistogramDataPoint.sum +* n *TimeSeries* each representing metric_name_bucket{le=“upperbound”} contain the count of each bucket defined by the bounds of the data point +* 1 *TimeSeries* representing metric_name_bucket{le=“+Inf”} contains counts for the bucket with infinity as upper bound; its value is equivalent to metric_name_count. + +Prometheus bucket values are cumulative, meaning the count of each bucket should contain counts from buckets with lower bounds. In addition, Exemplars from a histogram data point are ignored. When adding a bucket of the histogram data point to the map, the string signature should also contain a `le` label that indicates the bound value. This label should also be exported. Any histogram metric that is not cumulative should be dropped. + + +**SUMMARY** + +Each summary data point can be converted to 2 + n Prometheus remote write TimeSeries: + +* 1 *TimeSeries* representing metric_name_count contains SummaryDataPoint.count +* 1 *TimeSeries* representing metric_name_sum contains SummaryDataPoint.sum +* and n *TimeSeries* each representing metric_name{quantile=“quantileValue”} contains the value of each quantile in the data point. + +When adding a quantile of the summary data point to the map, the string signature should also contain a `quantile ` label that indicates the quantile value. This label should also be exported. Any summary metric that is not cumulative should be dropped. + +### **2.3 Exporting Metrics** + +The Prometheus remote write/Cortex exporter should call proto.Marshal() to convert multiple TimeSeries to a byte array. Then, the exporter should send the byte array to Prometheus remote storage in a HTTP request. + + +Authentication credentials should be added to each request before sending to the backend. Basic auth and bearer token headers can be added using Golang http.Client’s default configuration options. Other authentication headers can be added by implementing a client interceptor. + + +Pseudocode: + + + func export(*map) error { + // Stores timeseries + arr := make([]TimeSeries) + + for timeseries in map: + arr = append(arr, timeseries) + + // Converts arr to WriteRequest + request := proto.Marshal(arr) + + // Sends HTTP request to endpoint + } + +## **3. Other Components** + +### **3.1 Config Struct** + +This struct is based on an inputted YAML file at the beginning of the pipeline and defines the configurations for an Exporter build. Examples of configuration parameters are HTTP endpoint, compression type, backend program, etc. + + +Converting YAML to a Go struct is done by the Collector, using [_the Viper package_](https://github.com/spf13/viper), which is an open-source library that seamlessly converts inputted YAML files into a usable, appropriate Config struct. + + +An example of the exporter section of the Collector config.yml YAML file can be seen below: + + ... + + exporters: + prometheus_remote_write: + http_endpoint: + # Prefix to metric name + namespace: + # Labels to add to each TimeSeries + const_labels: + [label: ] + # Allow users to add any header; only required headers listed here + headers: + [X-Prometheus-Remote-Write-Version:] + [Tenant-id:] + request_timeout: + + # ************************************************************************ + # below are configurations copied from Prometheus remote write config + # ************************************************************************ + # Sets the `Authorization` header on every remote write request with the + # configured username and password. + # password and password_file are mutually exclusive. + basic_auth: + [ username: ] + [ password: ] + [ password_file: ] + + # Sets the `Authorization` header on every remote write request with + # the configured bearer token. It is mutually exclusive with `bearer_token_file`. + [ bearer_token: ] + + # Sets the `Authorization` header on every remote write request with the bearer token + # read from the configured file. It is mutually exclusive with `bearer_token`. + [ bearer_token_file: /path/to/bearer/token/file ] + + # Configures the remote write request's TLS settings. + tls_config: + # CA certificate to validate API server certificate with. + [ ca_file: ] + + # Certificate and key files for client cert authentication to the server. + [ cert_file: ] + [ key_file: ] + + # ServerName extension to indicate the name of the server. + # https://tools.ietf.org/html/rfc4366#section-3.1 + [ server_name: ] + + # Disable validation of the server certificate. + [ insecure_skip_verify: ] + + ... + +### **3.2 Factory Struct** + +This struct implements the ExporterFactory interface, and is used during collector’s pipeline initialization to create the Exporter instances as defined by the Config struct. The `exporterhelper` package will be used to create the exporter and the factory. + + +Our Factory type will look very similar to other exporters’ factory implementation. For our implementation, our Factory instance will implement three methods + + +**Methods** + + NewFactory +This method will use the NewFactory method within the `exporterhelper` package to create a instance of the factory. + + createDefaultConfig + +This method creates the default configuration for Prometheus remote write/Cortex exporter. + + + createMetricsExporter + +This method constructs a new http.Client with interceptors that add headers to any request it sends. Then, this method initializes a new Prometheus remote write exporter/Cortex exporter with the http.Client. This method constructs a collector Prometheus remote write/Cortex exporter exporter with the created SDK exporter + + + +## **4. Other Considerations** + +### **4.1 Concurrency** + +The Prometheus remote write/Cortex exporter should be thread-safe; In this design, the only resource shared across goroutines is the http.Client from the Golang library. It is thread-safe, thus, our code is thread-safe. + +### **4.2 Shutdown Behavior** + +Once the shutdown() function is called, the exporter should stop accepting incoming calls(return error), and wait for current operations to finish before returning. This can be done by using a stop channel and a wait group. + + func Shutdown () { + close(stopChan) + waitGroup.Wait() + } + + func PushMetrics() { + select: + case <- stopCh + return error + default: + waitGroup.Add(1) + defer waitGroup.Done() + // export metrics + ... + } + +### **4.3 Timeout Behavior** + +Users should be able to pass in a time for the each http request as part of the Configuration. The factory should read the configuration file and set the timeout field of the http.Client + + func (f *Factory) CreateNewExporter (config) { + ... + client := &http.Client{ + Timeout config.requestTimeout + } + ... + } + +### **4.4 Error Behavior** + +The PushMetricsData() function should return the number of dropped metrics. Any monotonic and histogram metrics that are not cumulative should be dropped. This can be done by checking the temporality of each received metric. Any error should be returned to the caller, and the error message should be descriptive. + + + +### **4.5 Test Strategy** + +We will follow test-driven development practices while completing this project. We’ll write unit tests before implementing production code. Tests will cover normal and abnormal inputs and test for edge cases. We will provide end-to-end tests using mock backend/client. Our target is to get 90% or more of code coverage. + + + +## **Request for Feedback** +We'd like to get some feedback on whether we made the appropriate assumptions in [this](#1.2-gaps-and-ssumptions) section, and appreciate more comments, updates , and suggestions on the topic. + +Please let us know if there are any revisions, technical or informational, necessary for this document. Thank you! + + + diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/README.md b/internal/otel_collector/exporter/prometheusremotewriteexporter/README.md new file mode 100644 index 00000000000..28e487f2f12 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/README.md @@ -0,0 +1,54 @@ +# Prometheus Remote Write Exporter + +This exporter sends data in Prometheus TimeSeries format to Cortex or any +Prometheus [remote write compatible +backend](https://prometheus.io/docs/operating/integrations/). +By default, this exporter requires TLS and offers queued retry capabilities. + +:warning: Non-cumulative monotonic, histogram, and summary OTLP metrics are +dropped by this exporter. + +_Here is a link to the overall project [design](./DESIGN.md)_ + +Supported pipeline types: metrics + +## Getting Started + +The following settings are required: + +- `endpoint` (no default): protocol:host:port to which the exporter is going to send data. + +By default, TLS is enabled: + +- `insecure` (default = `false`): whether to enable client transport security for + the exporter's connection. + +As a result, the following parameters are also required: + +- `cert_file` (no default): path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to false. +- `key_file` (no default): path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to false. + +The following settings can be optionally configured: + +- `external_labels`: list of labels to be attached to each metric data point +- `headers`: additional headers attached to each HTTP request. + - *Note the following headers cannot be changed: `Content-Encoding`, `Content-Type`, `X-Prometheus-Remote-Write-Version`, and `User-Agent`.* +- `namespace`: prefix attached to each exported metric name. + +Example: + +```yaml +exporters: + prometheusremotewrite: + endpoint: "http://some.url:9411/api/prom/push" +``` + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [HTTP settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/confighttp/README.md) +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/config.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/config.go new file mode 100644 index 00000000000..ea265be0ded --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/config.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// Config defines configuration for Remote Write exporter. +type Config struct { + // squash ensures fields are correctly decoded in embedded struct. + configmodels.ExporterSettings `mapstructure:",squash"` + exporterhelper.TimeoutSettings `mapstructure:",squash"` + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + // prefix attached to each exported metric name + // See: https://prometheus.io/docs/practices/naming/#metric-names + Namespace string `mapstructure:"namespace"` + + // ExternalLabels defines a map of label keys and values that are allowed to start with reserved prefix "__" + ExternalLabels map[string]string `mapstructure:"external_labels"` + + HTTPClientSettings confighttp.HTTPClientSettings `mapstructure:",squash"` +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/config_test.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/config_test.go new file mode 100644 index 00000000000..cfc7a9e638c --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/config_test.go @@ -0,0 +1,87 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// TestLoadConfig checks whether yaml configuration can be loaded correctly +func Test_loadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + // From the default configurations -- checks if a correct exporter is instantiated + e0 := cfg.Exporters["prometheusremotewrite"] + assert.Equal(t, e0, factory.CreateDefaultConfig()) + + // checks if the correct Config struct can be instantiated from testdata/config.yaml + e1 := cfg.Exporters["prometheusremotewrite/2"] + assert.Equal(t, e1, + &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "prometheusremotewrite/2", + TypeVal: "prometheusremotewrite", + }, + TimeoutSettings: exporterhelper.DefaultTimeoutSettings(), + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 2, + QueueSize: 10, + }, + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 10 * time.Second, + MaxInterval: 1 * time.Minute, + MaxElapsedTime: 10 * time.Minute, + }, + Namespace: "test-space", + ExternalLabels: map[string]string{"key1": "value1", "key2": "value2"}, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "localhost:8888", + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/var/lib/mycert.pem", // This is subject to change, but currently I have no idea what else to put here lol + }, + Insecure: false, + }, + ReadBufferSize: 0, + WriteBufferSize: 512 * 1024, + Timeout: 5 * time.Second, + Headers: map[string]string{ + "prometheus-remote-write-version": "0.1.0", + "x-scope-orgid": "234"}, + }, + }) +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/exporter.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/exporter.go new file mode 100644 index 00000000000..e56143c9f40 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/exporter.go @@ -0,0 +1,345 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package prometheusremotewriteexporter implements an exporter that sends Prometheus remote write requests. +package prometheusremotewriteexporter + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "math" + "net/http" + "net/url" + "sync" + + "github.com/gogo/protobuf/proto" + "github.com/golang/snappy" + "github.com/prometheus/prometheus/prompb" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + otlp "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" + "go.opentelemetry.io/collector/internal/version" +) + +const ( + maxConcurrentRequests = 5 + maxBatchByteSize = 3000000 +) + +// PrwExporter converts OTLP metrics to Prometheus remote write TimeSeries and sends them to a remote endpoint. +type PrwExporter struct { + namespace string + externalLabels map[string]string + endpointURL *url.URL + client *http.Client + wg *sync.WaitGroup + closeChan chan struct{} +} + +// NewPrwExporter initializes a new PrwExporter instance and sets fields accordingly. +// client parameter cannot be nil. +func NewPrwExporter(namespace string, endpoint string, client *http.Client, externalLabels map[string]string) (*PrwExporter, error) { + if client == nil { + return nil, errors.New("http client cannot be nil") + } + + sanitizedLabels, err := validateAndSanitizeExternalLabels(externalLabels) + if err != nil { + return nil, err + } + + endpointURL, err := url.ParseRequestURI(endpoint) + if err != nil { + return nil, errors.New("invalid endpoint") + } + + return &PrwExporter{ + namespace: namespace, + externalLabels: sanitizedLabels, + endpointURL: endpointURL, + client: client, + wg: new(sync.WaitGroup), + closeChan: make(chan struct{}), + }, nil +} + +// Shutdown stops the exporter from accepting incoming calls(and return error), and wait for current export operations +// to finish before returning +func (prwe *PrwExporter) Shutdown(context.Context) error { + close(prwe.closeChan) + prwe.wg.Wait() + return nil +} + +// PushMetrics converts metrics to Prometheus remote write TimeSeries and send to remote endpoint. It maintain a map of +// TimeSeries, validates and handles each individual metric, adding the converted TimeSeries to the map, and finally +// exports the map. +func (prwe *PrwExporter) PushMetrics(ctx context.Context, md pdata.Metrics) (int, error) { + prwe.wg.Add(1) + defer prwe.wg.Done() + + select { + case <-prwe.closeChan: + return md.MetricCount(), errors.New("shutdown has been called") + default: + tsMap := map[string]*prompb.TimeSeries{} + dropped := 0 + var errs []error + resourceMetrics := pdata.MetricsToOtlp(md) + for _, resourceMetric := range resourceMetrics { + if resourceMetric == nil { + continue + } + // TODO: add resource attributes as labels, probably in next PR + for _, instrumentationMetrics := range resourceMetric.InstrumentationLibraryMetrics { + if instrumentationMetrics == nil { + continue + } + // TODO: decide if instrumentation library information should be exported as labels + for _, metric := range instrumentationMetrics.Metrics { + if metric == nil { + dropped++ + continue + } + // check for valid type and temporality combination and for matching data field and type + if ok := validateMetrics(metric); !ok { + dropped++ + errs = append(errs, consumererror.Permanent(errors.New("invalid temporality and type combination"))) + continue + } + // handle individual metric based on type + switch metric.Data.(type) { + case *otlp.Metric_DoubleSum, *otlp.Metric_IntSum, *otlp.Metric_DoubleGauge, *otlp.Metric_IntGauge: + if err := prwe.handleScalarMetric(tsMap, metric); err != nil { + dropped++ + errs = append(errs, consumererror.Permanent(err)) + } + case *otlp.Metric_DoubleHistogram, *otlp.Metric_IntHistogram: + if err := prwe.handleHistogramMetric(tsMap, metric); err != nil { + dropped++ + errs = append(errs, consumererror.Permanent(err)) + } + case *otlp.Metric_DoubleSummary: + if err := prwe.handleSummaryMetric(tsMap, metric); err != nil { + dropped++ + errs = append(errs, consumererror.Permanent(err)) + } + default: + dropped++ + errs = append(errs, consumererror.Permanent(errors.New("unsupported metric type"))) + } + } + } + } + + if exportErrors := prwe.export(ctx, tsMap); len(exportErrors) != 0 { + dropped = md.MetricCount() + errs = append(errs, exportErrors...) + } + + if dropped != 0 { + return dropped, componenterror.CombineErrors(errs) + } + + return 0, nil + } +} + +func validateAndSanitizeExternalLabels(externalLabels map[string]string) (map[string]string, error) { + sanitizedLabels := make(map[string]string) + for key, value := range externalLabels { + if key == "" || value == "" { + return nil, fmt.Errorf("prometheus remote write: external labels configuration contains an empty key or value") + } + + // Sanitize label keys to meet Prometheus Requirements + if len(key) > 2 && key[:2] == "__" { + key = "__" + sanitize(key[2:]) + } else { + key = sanitize(key) + } + sanitizedLabels[key] = value + } + + return sanitizedLabels, nil +} + +// handleScalarMetric processes data points in a single OTLP scalar metric by adding the each point as a Sample into +// its corresponding TimeSeries in tsMap. +// tsMap and metric cannot be nil, and metric must have a non-nil descriptor +func (prwe *PrwExporter) handleScalarMetric(tsMap map[string]*prompb.TimeSeries, metric *otlp.Metric) error { + switch metric.Data.(type) { + // int points + case *otlp.Metric_DoubleGauge: + if metric.GetDoubleGauge().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetDoubleGauge().GetDataPoints() { + addSingleDoubleDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + case *otlp.Metric_IntGauge: + if metric.GetIntGauge().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetIntGauge().GetDataPoints() { + addSingleIntDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + case *otlp.Metric_DoubleSum: + if metric.GetDoubleSum().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetDoubleSum().GetDataPoints() { + addSingleDoubleDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + case *otlp.Metric_IntSum: + if metric.GetIntSum().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetIntSum().GetDataPoints() { + addSingleIntDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + } + return nil +} + +// handleHistogramMetric processes data points in a single OTLP histogram metric by mapping the sum, count and each +// bucket of every data point as a Sample, and adding each Sample to its corresponding TimeSeries. +// tsMap and metric cannot be nil. +func (prwe *PrwExporter) handleHistogramMetric(tsMap map[string]*prompb.TimeSeries, metric *otlp.Metric) error { + switch metric.Data.(type) { + case *otlp.Metric_IntHistogram: + if metric.GetIntHistogram().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetIntHistogram().GetDataPoints() { + addSingleIntHistogramDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + case *otlp.Metric_DoubleHistogram: + if metric.GetDoubleHistogram().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetDoubleHistogram().GetDataPoints() { + addSingleDoubleHistogramDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + } + return nil +} + +// handleSummaryMetric processes data points in a single OTLP summary metric by mapping the sum, count and each +// quantile of every data point as a Sample, and adding each Sample to its corresponding TimeSeries. +// tsMap and metric cannot be nil. +func (prwe *PrwExporter) handleSummaryMetric(tsMap map[string]*prompb.TimeSeries, metric *otlp.Metric) error { + if metric.GetDoubleSummary().GetDataPoints() == nil { + return fmt.Errorf("nil data point. %s is dropped", metric.GetName()) + } + for _, pt := range metric.GetDoubleSummary().GetDataPoints() { + addSingleDoubleSummaryDataPoint(pt, metric, prwe.namespace, tsMap, prwe.externalLabels) + } + return nil +} + +// export sends a Snappy-compressed WriteRequest containing TimeSeries to a remote write endpoint in order +func (prwe *PrwExporter) export(ctx context.Context, tsMap map[string]*prompb.TimeSeries) []error { + var errs []error + // Calls the helper function to convert and batch the TsMap to the desired format + requests, err := batchTimeSeries(tsMap, maxBatchByteSize) + if err != nil { + errs = append(errs, consumererror.Permanent(err)) + return errs + } + + input := make(chan *prompb.WriteRequest, len(requests)) + for _, request := range requests { + input <- request + } + close(input) + + var mu sync.Mutex + var wg sync.WaitGroup + + concurrencyLimit := int(math.Min(maxConcurrentRequests, float64(len(requests)))) + wg.Add(concurrencyLimit) // used to wait for workers to be finished + + // Run concurrencyLimit of workers until there + // is no more requests to execute in the input channel. + for i := 0; i < concurrencyLimit; i++ { + go func() { + defer wg.Done() + + for request := range input { + err := prwe.execute(ctx, request) + if err != nil { + mu.Lock() + errs = append(errs, err) + mu.Unlock() + } + } + }() + } + wg.Wait() + + return errs +} + +func (prwe *PrwExporter) execute(ctx context.Context, writeReq *prompb.WriteRequest) error { + // Uses proto.Marshal to convert the WriteRequest into bytes array + data, err := proto.Marshal(writeReq) + if err != nil { + return consumererror.Permanent(err) + } + buf := make([]byte, len(data), cap(data)) + compressedData := snappy.Encode(buf, data) + + // Create the HTTP POST request to send to the endpoint + req, err := http.NewRequestWithContext(ctx, "POST", prwe.endpointURL.String(), bytes.NewReader(compressedData)) + if err != nil { + return consumererror.Permanent(err) + } + + // Add necessary headers specified by: + // https://cortexmetrics.io/docs/apis/#remote-api + req.Header.Add("Content-Encoding", "snappy") + req.Header.Set("Content-Type", "application/x-protobuf") + req.Header.Set("X-Prometheus-Remote-Write-Version", "0.1.0") + req.Header.Set("User-Agent", "OpenTelemetry-Collector/"+version.Version) + + resp, err := prwe.client.Do(req) + if err != nil { + return consumererror.Permanent(err) + } + + // 2xx status code is considered a success + // 5xx errors are recoverable and the exporter should retry + // Reference for different behavior according to status code: + // https://github.com/prometheus/prometheus/pull/2552/files#diff-ae8db9d16d8057358e49d694522e7186 + if resp.StatusCode/100 != 2 { + scanner := bufio.NewScanner(io.LimitReader(resp.Body, 256)) + var line string + if scanner.Scan() { + line = scanner.Text() + } + err := fmt.Errorf("server returned HTTP status %v: %v ", resp.Status, line) + if resp.StatusCode >= 500 && resp.StatusCode < 600 { + return err + } + return consumererror.Permanent(err) + } + return nil +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/exporter_test.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/exporter_test.go new file mode 100644 index 00000000000..2333f40eda3 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/exporter_test.go @@ -0,0 +1,751 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "sync" + "testing" + + "github.com/gogo/protobuf/proto" + "github.com/golang/snappy" + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/exporterhelper" + otlp "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/internal/version" +) + +// Test_ NewPrwExporter checks that a new exporter instance with non-nil fields is initialized +func Test_NewPrwExporter(t *testing.T) { + config := &Config{ + ExporterSettings: configmodels.ExporterSettings{}, + TimeoutSettings: exporterhelper.TimeoutSettings{}, + QueueSettings: exporterhelper.QueueSettings{}, + RetrySettings: exporterhelper.RetrySettings{}, + Namespace: "", + ExternalLabels: map[string]string{}, + HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: ""}, + } + tests := []struct { + name string + config *Config + namespace string + endpoint string + externalLabels map[string]string + client *http.Client + returnError bool + }{ + { + "invalid_URL", + config, + "test", + "invalid URL", + map[string]string{"Key1": "Val1"}, + http.DefaultClient, + true, + }, + { + "nil_client", + config, + "test", + "http://some.url:9411/api/prom/push", + map[string]string{"Key1": "Val1"}, + nil, + true, + }, + { + "invalid_labels_case", + config, + "test", + "http://some.url:9411/api/prom/push", + map[string]string{"Key1": ""}, + http.DefaultClient, + true, + }, + { + "success_case", + config, + "test", + "http://some.url:9411/api/prom/push", + map[string]string{"Key1": "Val1"}, + http.DefaultClient, + false, + }, + { + "success_case_no_labels", + config, + "test", + "http://some.url:9411/api/prom/push", + map[string]string{}, + http.DefaultClient, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + prwe, err := NewPrwExporter(tt.namespace, tt.endpoint, tt.client, tt.externalLabels) + if tt.returnError { + assert.Error(t, err) + return + } + require.NotNil(t, prwe) + assert.NotNil(t, prwe.namespace) + assert.NotNil(t, prwe.endpointURL) + assert.NotNil(t, prwe.externalLabels) + assert.NotNil(t, prwe.client) + assert.NotNil(t, prwe.closeChan) + assert.NotNil(t, prwe.wg) + }) + } +} + +// Test_Shutdown checks after Shutdown is called, incoming calls to PushMetrics return error. +func Test_Shutdown(t *testing.T) { + prwe := &PrwExporter{ + wg: new(sync.WaitGroup), + closeChan: make(chan struct{}), + } + wg := new(sync.WaitGroup) + errChan := make(chan error, 5) + err := prwe.Shutdown(context.Background()) + require.NoError(t, err) + errChan = make(chan error, 5) + for i := 0; i < 5; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _, ok := prwe.PushMetrics(context.Background(), testdata.GenerateMetricsEmpty()) + errChan <- ok + }() + } + wg.Wait() + close(errChan) + for ok := range errChan { + assert.Error(t, ok) + } +} + +// Test whether or not the Server receives the correct TimeSeries. +// Currently considering making this test an iterative for loop of multiple TimeSeries much akin to Test_PushMetrics +func Test_export(t *testing.T) { + // First we will instantiate a dummy TimeSeries instance to pass into both the export call and compare the http request + labels := getPromLabels(label11, value11, label12, value12, label21, value21, label22, value22) + sample1 := getSample(floatVal1, msTime1) + sample2 := getSample(floatVal2, msTime2) + ts1 := getTimeSeries(labels, sample1, sample2) + handleFunc := func(w http.ResponseWriter, r *http.Request, code int) { + // The following is a handler function that reads the sent httpRequest, unmarshals, and checks if the WriteRequest + // preserves the TimeSeries data correctly + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + require.NotNil(t, body) + // Receives the http requests and unzip, unmarshals, and extracts TimeSeries + assert.Equal(t, "0.1.0", r.Header.Get("X-Prometheus-Remote-Write-Version")) + assert.Equal(t, "snappy", r.Header.Get("Content-Encoding")) + assert.Equal(t, "OpenTelemetry-Collector/"+version.Version, r.Header.Get("User-Agent")) + writeReq := &prompb.WriteRequest{} + unzipped := []byte{} + + dest, err := snappy.Decode(unzipped, body) + require.NoError(t, err) + + ok := proto.Unmarshal(dest, writeReq) + require.NoError(t, ok) + + assert.EqualValues(t, 1, len(writeReq.Timeseries)) + require.NotNil(t, writeReq.GetTimeseries()) + assert.Equal(t, *ts1, writeReq.GetTimeseries()[0]) + w.WriteHeader(code) + } + + // Create in test table format to check if different HTTP response codes or server errors + // are properly identified + tests := []struct { + name string + ts prompb.TimeSeries + serverUp bool + httpResponseCode int + returnError bool + }{ + {"success_case", + *ts1, + true, + http.StatusAccepted, + false, + }, + { + "server_no_response_case", + *ts1, + false, + http.StatusAccepted, + true, + }, { + "error_status_code_case", + *ts1, + true, + http.StatusForbidden, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if handleFunc != nil { + handleFunc(w, r, tt.httpResponseCode) + } + })) + defer server.Close() + serverURL, uErr := url.Parse(server.URL) + assert.NoError(t, uErr) + if !tt.serverUp { + server.Close() + } + errs := runExportPipeline(ts1, serverURL) + if tt.returnError { + assert.Error(t, errs[0]) + return + } + assert.Len(t, errs, 0) + }) + } +} + +func runExportPipeline(ts *prompb.TimeSeries, endpoint *url.URL) []error { + var errs []error + + // First we will construct a TimeSeries array from the testutils package + testmap := make(map[string]*prompb.TimeSeries) + testmap["test"] = ts + + HTTPClient := http.DefaultClient + // after this, instantiate a CortexExporter with the current HTTP client and endpoint set to passed in endpoint + prwe, err := NewPrwExporter("test", endpoint.String(), HTTPClient, map[string]string{}) + if err != nil { + errs = append(errs, err) + return errs + } + errs = append(errs, prwe.export(context.Background(), testmap)...) + return errs +} + +// Test_PushMetrics checks the number of TimeSeries received by server and the number of metrics dropped is the same as +// expected +func Test_PushMetrics(t *testing.T) { + + invalidTypeBatch := testdata.GenerateMetricsMetricTypeInvalid() + + // success cases + intSumBatch := testdata.GenerateMetricsManyMetricsSameResource(10) + + doubleSumMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics1[validDoubleSum], + validMetrics2[validDoubleSum], + }, + }, + }, + }, + } + doubleSumBatch := pdata.MetricsFromOtlp(doubleSumMetric) + + intGaugeMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics1[validIntGauge], + validMetrics2[validIntGauge], + }, + }, + }, + }, + } + intGaugeBatch := pdata.MetricsFromOtlp(intGaugeMetric) + + doubleGaugeMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics1[validDoubleGauge], + validMetrics2[validDoubleGauge], + }, + }, + }, + }, + } + doubleGaugeBatch := pdata.MetricsFromOtlp(doubleGaugeMetric) + + intHistogramMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics1[validIntHistogram], + validMetrics2[validIntHistogram], + }, + }, + }, + }, + } + intHistogramBatch := pdata.MetricsFromOtlp(intHistogramMetric) + + doubleHistogramMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics1[validDoubleHistogram], + validMetrics2[validDoubleHistogram], + }, + }, + }, + }, + } + doubleHistogramBatch := pdata.MetricsFromOtlp(doubleHistogramMetric) + + doubleSummaryMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics1[validDoubleSummary], + validMetrics2[validDoubleSummary], + }, + }, + }, + }, + } + doubleSummaryBatch := pdata.MetricsFromOtlp(doubleSummaryMetric) + + // len(BucketCount) > len(ExplicitBounds) + unmatchedBoundBucketIntHistMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics2[unmatchedBoundBucketIntHist], + }, + }, + }, + }, + } + unmatchedBoundBucketIntHistBatch := pdata.MetricsFromOtlp(unmatchedBoundBucketIntHistMetric) + + unmatchedBoundBucketDoubleHistMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + validMetrics2[unmatchedBoundBucketDoubleHist], + }, + }, + }, + }, + } + unmatchedBoundBucketDoubleHistBatch := pdata.MetricsFromOtlp(unmatchedBoundBucketDoubleHistMetric) + + // fail cases + nilDataPointIntGaugeMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointIntGauge], + }, + }, + }, + }, + } + nilDataPointIntGaugeBatch := pdata.MetricsFromOtlp(nilDataPointIntGaugeMetric) + + nilDataPointDoubleGaugeMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointDoubleGauge], + }, + }, + }, + }, + } + nilDataPointDoubleGaugeBatch := pdata.MetricsFromOtlp(nilDataPointDoubleGaugeMetric) + + nilDataPointIntSumMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointIntSum], + }, + }, + }, + }, + } + nilDataPointIntSumBatch := pdata.MetricsFromOtlp(nilDataPointIntSumMetric) + + nilDataPointDoubleSumMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointDoubleSum], + }, + }, + }, + }, + } + nilDataPointDoubleSumBatch := pdata.MetricsFromOtlp(nilDataPointDoubleSumMetric) + + nilDataPointIntHistogramMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointIntHistogram], + }, + }, + }, + }, + } + nilDataPointIntHistogramBatch := pdata.MetricsFromOtlp(nilDataPointIntHistogramMetric) + + nilDataPointDoubleHistogramMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointDoubleHistogram], + }, + }, + }, + }, + } + nilDataPointDoubleHistogramBatch := pdata.MetricsFromOtlp(nilDataPointDoubleHistogramMetric) + + nilDataPointDoubleSummaryMetric := []*otlp.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlp.InstrumentationLibraryMetrics{ + { + Metrics: []*otlp.Metric{ + errorMetrics[nilDataPointDoubleSummary], + }, + }, + }, + }, + } + nilDataPointDoubleSummaryBatch := pdata.MetricsFromOtlp(nilDataPointDoubleSummaryMetric) + + checkFunc := func(t *testing.T, r *http.Request, expected int) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, len(body)) + dest, err := snappy.Decode(buf, body) + assert.Equal(t, "0.1.0", r.Header.Get("x-prometheus-remote-write-version")) + assert.Equal(t, "snappy", r.Header.Get("content-encoding")) + assert.Equal(t, "OpenTelemetry-Collector/"+version.Version, r.Header.Get("user-agent")) + assert.NotNil(t, r.Header.Get("tenant-id")) + require.NoError(t, err) + wr := &prompb.WriteRequest{} + ok := proto.Unmarshal(dest, wr) + require.Nil(t, ok) + assert.EqualValues(t, expected, len(wr.Timeseries)) + } + + tests := []struct { + name string + md *pdata.Metrics + reqTestFunc func(t *testing.T, r *http.Request, expected int) + expectedTimeSeries int + httpResponseCode int + numDroppedTimeSeries int + returnErr bool + }{ + { + "invalid_type_case", + &invalidTypeBatch, + nil, + 0, + http.StatusAccepted, + invalidTypeBatch.MetricCount(), + true, + }, + { + "intSum_case", + &intSumBatch, + checkFunc, + 2, + http.StatusAccepted, + 0, + false, + }, + { + "doubleSum_case", + &doubleSumBatch, + checkFunc, + 2, + http.StatusAccepted, + 0, + false, + }, + { + "doubleGauge_case", + &doubleGaugeBatch, + checkFunc, + 2, + http.StatusAccepted, + 0, + false, + }, + { + "intGauge_case", + &intGaugeBatch, + checkFunc, + 2, + http.StatusAccepted, + 0, + false, + }, + { + "intHistogram_case", + &intHistogramBatch, + checkFunc, + 12, + http.StatusAccepted, + 0, + false, + }, + { + "doubleHistogram_case", + &doubleHistogramBatch, + checkFunc, + 12, + http.StatusAccepted, + 0, + false, + }, + { + "doubleSummary_case", + &doubleSummaryBatch, + checkFunc, + 10, + http.StatusAccepted, + 0, + false, + }, + { + "unmatchedBoundBucketIntHist_case", + &unmatchedBoundBucketIntHistBatch, + checkFunc, + 5, + http.StatusAccepted, + 0, + false, + }, + { + "unmatchedBoundBucketDoubleHist_case", + &unmatchedBoundBucketDoubleHistBatch, + checkFunc, + 5, + http.StatusAccepted, + 0, + false, + }, + { + "5xx_case", + &unmatchedBoundBucketDoubleHistBatch, + checkFunc, + 5, + http.StatusServiceUnavailable, + 1, + true, + }, + { + "nilDataPointDoubleGauge_case", + &nilDataPointDoubleGaugeBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointDoubleGaugeBatch.MetricCount(), + true, + }, + { + "nilDataPointIntGauge_case", + &nilDataPointIntGaugeBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointIntGaugeBatch.MetricCount(), + true, + }, + { + "nilDataPointDoubleSum_case", + &nilDataPointDoubleSumBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointDoubleSumBatch.MetricCount(), + true, + }, + { + "nilDataPointIntSum_case", + &nilDataPointIntSumBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointIntSumBatch.MetricCount(), + true, + }, + { + "nilDataPointDoubleHistogram_case", + &nilDataPointDoubleHistogramBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointDoubleHistogramBatch.MetricCount(), + true, + }, + { + "nilDataPointIntHistogram_case", + &nilDataPointIntHistogramBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointIntHistogramBatch.MetricCount(), + true, + }, + { + "nilDataPointDoubleSummary_case", + &nilDataPointDoubleSummaryBatch, + checkFunc, + 0, + http.StatusAccepted, + nilDataPointDoubleSummaryBatch.MetricCount(), + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if tt.reqTestFunc != nil { + tt.reqTestFunc(t, r, tt.expectedTimeSeries) + } + w.WriteHeader(tt.httpResponseCode) + })) + + defer server.Close() + + serverURL, uErr := url.Parse(server.URL) + assert.NoError(t, uErr) + + config := &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: "prometheusremotewrite", + NameVal: "prometheusremotewrite", + }, + Namespace: "", + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "http://some.url:9411/api/prom/push", + // We almost read 0 bytes, so no need to tune ReadBufferSize. + ReadBufferSize: 0, + WriteBufferSize: 512 * 1024, + }, + } + assert.NotNil(t, config) + // c, err := config.HTTPClientSettings.ToClient() + // assert.Nil(t, err) + c := http.DefaultClient + prwe, nErr := NewPrwExporter(config.Namespace, serverURL.String(), c, map[string]string{}) + require.NoError(t, nErr) + numDroppedTimeSeries, err := prwe.PushMetrics(context.Background(), *tt.md) + assert.Equal(t, tt.numDroppedTimeSeries, numDroppedTimeSeries) + if tt.returnErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + }) + } +} + +func Test_validateAndSanitizeExternalLabels(t *testing.T) { + tests := []struct { + name string + inputLabels map[string]string + expectedLabels map[string]string + returnError bool + }{ + {"success_case_no_labels", + map[string]string{}, + map[string]string{}, + false, + }, + {"success_case_with_labels", + map[string]string{"key1": "val1"}, + map[string]string{"key1": "val1"}, + false, + }, + {"success_case_2_with_labels", + map[string]string{"__key1__": "val1"}, + map[string]string{"__key1__": "val1"}, + false, + }, + {"success_case_with_sanitized_labels", + map[string]string{"__key1.key__": "val1"}, + map[string]string{"__key1_key__": "val1"}, + false, + }, + {"fail_case_empty_label", + map[string]string{"": "val1"}, + map[string]string{}, + true, + }, + } + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + newLabels, err := validateAndSanitizeExternalLabels(tt.inputLabels) + if tt.returnError { + assert.Error(t, err) + return + } + assert.EqualValues(t, tt.expectedLabels, newLabels) + assert.NoError(t, err) + }) + } +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/factory.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/factory.go new file mode 100644 index 00000000000..af2c65154ab --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/factory.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "context" + "errors" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "prometheusremotewrite" +) + +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithMetrics(createMetricsExporter)) +} + +func createMetricsExporter(_ context.Context, params component.ExporterCreateParams, + cfg configmodels.Exporter) (component.MetricsExporter, error) { + + prwCfg, ok := cfg.(*Config) + if !ok { + return nil, errors.New("invalid configuration") + } + + client, err := prwCfg.HTTPClientSettings.ToClient() + if err != nil { + return nil, err + } + + prwe, err := NewPrwExporter(prwCfg.Namespace, prwCfg.HTTPClientSettings.Endpoint, client, prwCfg.ExternalLabels) + if err != nil { + return nil, err + } + + prwexp, err := exporterhelper.NewMetricsExporter( + cfg, + params.Logger, + prwe.PushMetrics, + exporterhelper.WithTimeout(prwCfg.TimeoutSettings), + exporterhelper.WithQueue(prwCfg.QueueSettings), + exporterhelper.WithRetry(prwCfg.RetrySettings), + exporterhelper.WithShutdown(prwe.Shutdown), + ) + + return prwexp, err +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Namespace: "", + ExternalLabels: map[string]string{}, + TimeoutSettings: exporterhelper.DefaultTimeoutSettings(), + RetrySettings: exporterhelper.DefaultRetrySettings(), + QueueSettings: exporterhelper.DefaultQueueSettings(), + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "http://some.url:9411/api/prom/push", + // We almost read 0 bytes, so no need to tune ReadBufferSize. + ReadBufferSize: 0, + WriteBufferSize: 512 * 1024, + Timeout: exporterhelper.DefaultTimeoutSettings().Timeout, + Headers: map[string]string{}, + }, + } +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/factory_test.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/factory_test.go new file mode 100644 index 00000000000..a94cb6ce83b --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/factory_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtls" +) + +// Tests whether or not the default Exporter factory can instantiate a properly interfaced Exporter with default conditions +func Test_createDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +// Tests whether or not a correct Metrics Exporter from the default Config parameters +func Test_createMetricsExporter(t *testing.T) { + + invalidConfig := createDefaultConfig().(*Config) + invalidConfig.HTTPClientSettings = confighttp.HTTPClientSettings{} + invalidTLSConfig := createDefaultConfig().(*Config) + invalidTLSConfig.HTTPClientSettings.TLSSetting = configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "non-existent file", + CertFile: "", + KeyFile: "", + }, + Insecure: false, + ServerName: "", + } + tests := []struct { + name string + cfg configmodels.Exporter + params component.ExporterCreateParams + returnError bool + }{ + {"success_case", + createDefaultConfig(), + component.ExporterCreateParams{Logger: zap.NewNop()}, + false, + }, + {"fail_case", + nil, + component.ExporterCreateParams{Logger: zap.NewNop()}, + true, + }, + {"invalid_config_case", + invalidConfig, + component.ExporterCreateParams{Logger: zap.NewNop()}, + true, + }, + {"invalid_tls_config_case", + invalidTLSConfig, + component.ExporterCreateParams{Logger: zap.NewNop()}, + true, + }, + } + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := createMetricsExporter(context.Background(), tt.params, tt.cfg) + if tt.returnError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/helper.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/helper.go new file mode 100644 index 00000000000..b19b820c0b1 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/helper.go @@ -0,0 +1,489 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "errors" + "log" + "sort" + "strconv" + "strings" + "time" + "unicode" + + "github.com/prometheus/prometheus/prompb" + + "go.opentelemetry.io/collector/consumer/pdata" + common "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlp "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +const ( + nameStr = "__name__" + sumStr = "_sum" + countStr = "_count" + bucketStr = "_bucket" + leStr = "le" + quantileStr = "quantile" + pInfStr = "+Inf" + totalStr = "total" + delimeter = "_" + keyStr = "key" +) + +// ByLabelName enables the usage of sort.Sort() with a slice of labels +type ByLabelName []prompb.Label + +func (a ByLabelName) Len() int { return len(a) } +func (a ByLabelName) Less(i, j int) bool { return a[i].Name < a[j].Name } +func (a ByLabelName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// validateMetrics returns a bool representing whether the metric has a valid type and temporality combination and a +// matching metric type and field +func validateMetrics(metric *otlp.Metric) bool { + if metric == nil || metric.Data == nil { + return false + } + switch metric.Data.(type) { + case *otlp.Metric_DoubleGauge: + return metric.GetDoubleGauge() != nil + case *otlp.Metric_IntGauge: + return metric.GetIntGauge() != nil + case *otlp.Metric_DoubleSum: + return metric.GetDoubleSum() != nil && metric.GetDoubleSum().GetAggregationTemporality() == + otlp.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE + case *otlp.Metric_IntSum: + return metric.GetIntSum() != nil && metric.GetIntSum().GetAggregationTemporality() == + otlp.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE + case *otlp.Metric_DoubleHistogram: + return metric.GetDoubleHistogram() != nil && metric.GetDoubleHistogram().GetAggregationTemporality() == + otlp.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE + case *otlp.Metric_IntHistogram: + return metric.GetIntHistogram() != nil && metric.GetIntHistogram().GetAggregationTemporality() == + otlp.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE + case *otlp.Metric_DoubleSummary: + return metric.GetDoubleSummary() != nil + } + return false +} + +// addSample finds a TimeSeries in tsMap that corresponds to the label set labels, and add sample to the TimeSeries; it +// creates a new TimeSeries in the map if not found. tsMap is unmodified if either of its parameters is nil. +func addSample(tsMap map[string]*prompb.TimeSeries, sample *prompb.Sample, labels []prompb.Label, + metric *otlp.Metric) { + + if sample == nil || labels == nil || tsMap == nil { + return + } + + sig := timeSeriesSignature(metric, &labels) + ts, ok := tsMap[sig] + + if ok { + ts.Samples = append(ts.Samples, *sample) + } else { + newTs := &prompb.TimeSeries{ + Labels: labels, + Samples: []prompb.Sample{*sample}, + } + tsMap[sig] = newTs + } +} + +// timeSeries return a string signature in the form of: +// TYPE-label1-value1- ... -labelN-valueN +// the label slice should not contain duplicate label names; this method sorts the slice by label name before creating +// the signature. +func timeSeriesSignature(metric *otlp.Metric, labels *[]prompb.Label) string { + b := strings.Builder{} + b.WriteString(getTypeString(metric)) + + sort.Sort(ByLabelName(*labels)) + + for _, lb := range *labels { + b.WriteString("-") + b.WriteString(lb.GetName()) + b.WriteString("-") + b.WriteString(lb.GetValue()) + } + + return b.String() +} + +// createLabelSet creates a slice of Cortex Label with OTLP labels and paris of string values. +// Unpaired string value is ignored. String pairs overwrites OTLP labels if collision happens, and the overwrite is +// logged. Resultant label names are sanitized. +func createLabelSet(labels []common.StringKeyValue, externalLabels map[string]string, extras ...string) []prompb.Label { + // map ensures no duplicate label name + l := map[string]prompb.Label{} + + for key, value := range externalLabels { + // External labels have already been sanitized + l[key] = prompb.Label{ + Name: key, + Value: value, + } + } + + for _, lb := range labels { + l[lb.Key] = prompb.Label{ + Name: sanitize(lb.Key), + Value: lb.Value, + } + } + + for i := 0; i < len(extras); i += 2 { + if i+1 >= len(extras) { + break + } + _, found := l[extras[i]] + if found { + log.Println("label " + extras[i] + " is overwritten. Check if Prometheus reserved labels are used.") + } + // internal labels should be maintained + name := extras[i] + if !(len(name) > 4 && name[:2] == "__" && name[len(name)-2:] == "__") { + name = sanitize(name) + } + l[extras[i]] = prompb.Label{ + Name: name, + Value: extras[i+1], + } + } + + s := make([]prompb.Label, 0, len(l)) + for _, lb := range l { + s = append(s, lb) + } + + return s +} + +// getPromMetricName creates a Prometheus metric name by attaching namespace prefix, and _total suffix for Monotonic +// metrics. +func getPromMetricName(metric *otlp.Metric, ns string) string { + if metric == nil { + return "" + } + + // if the metric is counter, _total suffix should be applied + _, isCounter1 := metric.Data.(*otlp.Metric_DoubleSum) + _, isCounter2 := metric.Data.(*otlp.Metric_IntSum) + isCounter := isCounter1 || isCounter2 + + b := strings.Builder{} + + b.WriteString(ns) + + if b.Len() > 0 { + b.WriteString(delimeter) + } + name := metric.GetName() + b.WriteString(name) + + // do not add the total suffix if the metric name already ends in "total" + isCounter = isCounter && name[len(name)-len(totalStr):] != totalStr + + // Including units makes two metrics with the same name and label set belong to two different TimeSeries if the + // units are different. + /* + if b.Len() > 0 && len(desc.GetUnit()) > 0{ + fmt.Fprintf(&b, delimeter) + fmt.Fprintf(&b, desc.GetUnit()) + } + */ + + if b.Len() > 0 && isCounter { + b.WriteString(delimeter) + b.WriteString(totalStr) + } + return sanitize(b.String()) +} + +// batchTimeSeries splits series into multiple batch write requests. +func batchTimeSeries(tsMap map[string]*prompb.TimeSeries, maxBatchByteSize int) ([]*prompb.WriteRequest, error) { + if len(tsMap) == 0 { + return nil, errors.New("invalid tsMap: cannot be empty map") + } + + var requests []*prompb.WriteRequest + var tsArray []prompb.TimeSeries + sizeOfCurrentBatch := 0 + + for _, v := range tsMap { + sizeOfSeries := v.Size() + + if sizeOfCurrentBatch+sizeOfSeries >= maxBatchByteSize { + wrapped := convertTimeseriesToRequest(tsArray) + requests = append(requests, wrapped) + + tsArray = make([]prompb.TimeSeries, 0) + sizeOfCurrentBatch = 0 + } + + tsArray = append(tsArray, *v) + sizeOfCurrentBatch += sizeOfSeries + } + + if len(tsArray) != 0 { + wrapped := convertTimeseriesToRequest(tsArray) + requests = append(requests, wrapped) + } + + return requests, nil +} + +// convertTimeStamp converts OTLP timestamp in ns to timestamp in ms +func convertTimeStamp(timestamp uint64) int64 { + return int64(timestamp / uint64(int64(time.Millisecond)/int64(time.Nanosecond))) +} + +// copied from prometheus-go-metric-exporter +// sanitize replaces non-alphanumeric characters with underscores in s. +func sanitize(s string) string { + if len(s) == 0 { + return s + } + + // Note: No length limit for label keys because Prometheus doesn't + // define a length limit, thus we should NOT be truncating label keys. + // See https://github.com/orijtech/prometheus-go-metrics-exporter/issues/4. + s = strings.Map(sanitizeRune, s) + if unicode.IsDigit(rune(s[0])) { + s = keyStr + delimeter + s + } + if s[0] == '_' { + s = keyStr + s + } + return s +} + +// copied from prometheus-go-metric-exporter +// sanitizeRune converts anything that is not a letter or digit to an underscore +func sanitizeRune(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + return r + } + // Everything else turns into an underscore + return '_' +} + +func getTypeString(metric *otlp.Metric) string { + switch metric.Data.(type) { + case *otlp.Metric_DoubleGauge: + return strconv.Itoa(int(pdata.MetricDataTypeDoubleGauge)) + case *otlp.Metric_IntGauge: + return strconv.Itoa(int(pdata.MetricDataTypeIntGauge)) + case *otlp.Metric_DoubleSum: + return strconv.Itoa(int(pdata.MetricDataTypeDoubleSum)) + case *otlp.Metric_IntSum: + return strconv.Itoa(int(pdata.MetricDataTypeIntSum)) + case *otlp.Metric_DoubleHistogram: + return strconv.Itoa(int(pdata.MetricDataTypeDoubleHistogram)) + case *otlp.Metric_IntHistogram: + return strconv.Itoa(int(pdata.MetricDataTypeIntHistogram)) + } + return "" +} + +// addSingleDoubleDataPoint converts the metric value stored in pt to a Prometheus sample, and add the sample +// to its corresponding time series in tsMap +func addSingleDoubleDataPoint(pt *otlp.DoubleDataPoint, metric *otlp.Metric, namespace string, + tsMap map[string]*prompb.TimeSeries, externalLabels map[string]string) { + if pt == nil { + return + } + // create parameters for addSample + name := getPromMetricName(metric, namespace) + labels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, name) + sample := &prompb.Sample{ + Value: pt.Value, + // convert ns to ms + Timestamp: convertTimeStamp(pt.TimeUnixNano), + } + addSample(tsMap, sample, labels, metric) +} + +// addSingleIntDataPoint converts the metric value stored in pt to a Prometheus sample, and add the sample +// to its corresponding time series in tsMap +func addSingleIntDataPoint(pt *otlp.IntDataPoint, metric *otlp.Metric, namespace string, + tsMap map[string]*prompb.TimeSeries, externalLabels map[string]string) { + if pt == nil { + return + } + // create parameters for addSample + name := getPromMetricName(metric, namespace) + labels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, name) + sample := &prompb.Sample{ + Value: float64(pt.Value), + // convert ns to ms + Timestamp: convertTimeStamp(pt.TimeUnixNano), + } + addSample(tsMap, sample, labels, metric) +} + +// addSingleIntHistogramDataPoint converts pt to 2 + min(len(ExplicitBounds), len(BucketCount)) + 1 samples. It +// ignore extra buckets if len(ExplicitBounds) > len(BucketCounts) +func addSingleIntHistogramDataPoint(pt *otlp.IntHistogramDataPoint, metric *otlp.Metric, namespace string, + tsMap map[string]*prompb.TimeSeries, externalLabels map[string]string) { + if pt == nil { + return + } + time := convertTimeStamp(pt.TimeUnixNano) + // sum, count, and buckets of the histogram should append suffix to baseName + baseName := getPromMetricName(metric, namespace) + // treat sum as a sample in an individual TimeSeries + sum := &prompb.Sample{ + Value: float64(pt.GetSum()), + Timestamp: time, + } + + sumlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+sumStr) + addSample(tsMap, sum, sumlabels, metric) + + // treat count as a sample in an individual TimeSeries + count := &prompb.Sample{ + Value: float64(pt.GetCount()), + Timestamp: time, + } + countlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+countStr) + addSample(tsMap, count, countlabels, metric) + + // cumulative count for conversion to cumulative histogram + var cumulativeCount uint64 + + // process each bound, ignore extra bucket values + for index, bound := range pt.GetExplicitBounds() { + if index >= len(pt.GetBucketCounts()) { + break + } + cumulativeCount += pt.GetBucketCounts()[index] + bucket := &prompb.Sample{ + Value: float64(cumulativeCount), + Timestamp: time, + } + boundStr := strconv.FormatFloat(bound, 'f', -1, 64) + labels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+bucketStr, leStr, boundStr) + addSample(tsMap, bucket, labels, metric) + } + // add le=+Inf bucket + cumulativeCount += pt.GetBucketCounts()[len(pt.GetBucketCounts())-1] + infBucket := &prompb.Sample{ + Value: float64(cumulativeCount), + Timestamp: time, + } + infLabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+bucketStr, leStr, pInfStr) + addSample(tsMap, infBucket, infLabels, metric) +} + +// addSingleDoubleHistogramDataPoint converts pt to 2 + min(len(ExplicitBounds), len(BucketCount)) + 1 samples. It +// ignore extra buckets if len(ExplicitBounds) > len(BucketCounts) +func addSingleDoubleHistogramDataPoint(pt *otlp.DoubleHistogramDataPoint, metric *otlp.Metric, namespace string, + tsMap map[string]*prompb.TimeSeries, externalLabels map[string]string) { + if pt == nil { + return + } + time := convertTimeStamp(pt.TimeUnixNano) + // sum, count, and buckets of the histogram should append suffix to baseName + baseName := getPromMetricName(metric, namespace) + // treat sum as a sample in an individual TimeSeries + sum := &prompb.Sample{ + Value: pt.GetSum(), + Timestamp: time, + } + + sumlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+sumStr) + addSample(tsMap, sum, sumlabels, metric) + + // treat count as a sample in an individual TimeSeries + count := &prompb.Sample{ + Value: float64(pt.GetCount()), + Timestamp: time, + } + countlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+countStr) + addSample(tsMap, count, countlabels, metric) + + // cumulative count for conversion to cumulative histogram + var cumulativeCount uint64 + + // process each bound, based on histograms proto definition, # of buckets = # of explicit bounds + 1 + for index, bound := range pt.GetExplicitBounds() { + if index >= len(pt.GetBucketCounts()) { + break + } + cumulativeCount += pt.GetBucketCounts()[index] + bucket := &prompb.Sample{ + Value: float64(cumulativeCount), + Timestamp: time, + } + boundStr := strconv.FormatFloat(bound, 'f', -1, 64) + labels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+bucketStr, leStr, boundStr) + addSample(tsMap, bucket, labels, metric) + } + // add le=+Inf bucket + cumulativeCount += pt.GetBucketCounts()[len(pt.GetBucketCounts())-1] + infBucket := &prompb.Sample{ + Value: float64(cumulativeCount), + Timestamp: time, + } + infLabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+bucketStr, leStr, pInfStr) + addSample(tsMap, infBucket, infLabels, metric) +} + +// addSingleDoubleSummaryDataPoint converts pt to len(QuantileValues) + 2 samples. +func addSingleDoubleSummaryDataPoint(pt *otlp.DoubleSummaryDataPoint, metric *otlp.Metric, namespace string, + tsMap map[string]*prompb.TimeSeries, externalLabels map[string]string) { + if pt == nil { + return + } + time := convertTimeStamp(pt.TimeUnixNano) + // sum and count of the summary should append suffix to baseName + baseName := getPromMetricName(metric, namespace) + // treat sum as a sample in an individual TimeSeries + sum := &prompb.Sample{ + Value: pt.GetSum(), + Timestamp: time, + } + + sumlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+sumStr) + addSample(tsMap, sum, sumlabels, metric) + + // treat count as a sample in an individual TimeSeries + count := &prompb.Sample{ + Value: float64(pt.GetCount()), + Timestamp: time, + } + countlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName+countStr) + addSample(tsMap, count, countlabels, metric) + + // process each percentile/quantile + for _, qt := range pt.GetQuantileValues() { + quantile := &prompb.Sample{ + Value: qt.Value, + Timestamp: time, + } + percentileStr := strconv.FormatFloat(qt.GetQuantile(), 'f', -1, 64) + qtlabels := createLabelSet(pt.GetLabels(), externalLabels, nameStr, baseName, quantileStr, percentileStr) + addSample(tsMap, quantile, qtlabels, metric) + } +} + +func convertTimeseriesToRequest(tsArray []prompb.TimeSeries) *prompb.WriteRequest { + // the remote_write endpoint only requires the timeseries. + // otlp defines it's own way to handle metric metadata + return &prompb.WriteRequest{ + Timeseries: tsArray, + } +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/helper_test.go b/internal/otel_collector/exporter/prometheusremotewriteexporter/helper_test.go new file mode 100644 index 00000000000..821d9947fe4 --- /dev/null +++ b/internal/otel_collector/exporter/prometheusremotewriteexporter/helper_test.go @@ -0,0 +1,363 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusremotewriteexporter + +import ( + "strconv" + "testing" + + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + common "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlp "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +// Test_validateMetrics checks validateMetrics return true if a type and temporality combination is valid, false +// otherwise. +func Test_validateMetrics(t *testing.T) { + + // define a single test + type combTest struct { + name string + metric *otlp.Metric + want bool + } + + tests := []combTest{} + + // append true cases + for k, validMetric := range validMetrics1 { + name := "valid_" + k + + tests = append(tests, combTest{ + name, + validMetric, + true, + }) + } + + // append nil case + tests = append(tests, combTest{"invalid_nil", nil, false}) + + for k, invalidMetric := range invalidMetrics { + name := "valid_" + k + + tests = append(tests, combTest{ + name, + invalidMetric, + false, + }) + } + + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validateMetrics(tt.metric) + assert.Equal(t, tt.want, got) + }) + } +} + +// Test_addSample checks addSample updates the map it receives correctly based on the sample and Label +// set it receives. +// Test cases are two samples belonging to the same TimeSeries, two samples belong to different TimeSeries, and nil +// case. +func Test_addSample(t *testing.T) { + type testCase struct { + metric *otlp.Metric + sample prompb.Sample + labels []prompb.Label + } + + tests := []struct { + name string + orig map[string]*prompb.TimeSeries + testCase []testCase + want map[string]*prompb.TimeSeries + }{ + { + "two_points_same_ts_same_metric", + map[string]*prompb.TimeSeries{}, + []testCase{ + {validMetrics1[validDoubleGauge], + getSample(floatVal1, msTime1), + promLbs1, + }, + { + validMetrics1[validDoubleGauge], + getSample(floatVal2, msTime2), + promLbs1, + }, + }, + twoPointsSameTs, + }, + { + "two_points_different_ts_same_metric", + map[string]*prompb.TimeSeries{}, + []testCase{ + {validMetrics1[validIntGauge], + getSample(float64(intVal1), msTime1), + promLbs1, + }, + {validMetrics1[validIntGauge], + getSample(float64(intVal1), msTime2), + promLbs2, + }, + }, + twoPointsDifferentTs, + }, + } + t.Run("nil_case", func(t *testing.T) { + tsMap := map[string]*prompb.TimeSeries{} + addSample(tsMap, nil, nil, nil) + assert.Exactly(t, tsMap, map[string]*prompb.TimeSeries{}) + }) + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addSample(tt.orig, &tt.testCase[0].sample, tt.testCase[0].labels, tt.testCase[0].metric) + addSample(tt.orig, &tt.testCase[1].sample, tt.testCase[1].labels, tt.testCase[1].metric) + assert.Exactly(t, tt.want, tt.orig) + }) + } +} + +// Test_timeSeries checks timeSeriesSignature returns consistent and unique signatures for a distinct label set and +// metric type combination. +func Test_timeSeriesSignature(t *testing.T) { + tests := []struct { + name string + lbs []prompb.Label + metric *otlp.Metric + want string + }{ + { + "int64_signature", + promLbs1, + validMetrics1[validIntGauge], + strconv.Itoa(int(pdata.MetricDataTypeIntGauge)) + lb1Sig, + }, + { + "histogram_signature", + promLbs2, + validMetrics1[validIntHistogram], + strconv.Itoa(int(pdata.MetricDataTypeIntHistogram)) + lb2Sig, + }, + { + "unordered_signature", + getPromLabels(label22, value22, label21, value21), + validMetrics1[validIntHistogram], + strconv.Itoa(int(pdata.MetricDataTypeIntHistogram)) + lb2Sig, + }, + // descriptor type cannot be nil, as checked by validateMetrics + { + "nil_case", + nil, + validMetrics1[validIntHistogram], + strconv.Itoa(int(pdata.MetricDataTypeIntHistogram)), + }, + } + + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.EqualValues(t, tt.want, timeSeriesSignature(tt.metric, &tt.lbs)) + }) + } +} + +// Test_createLabelSet checks resultant label names are sanitized and label in extra overrides label in labels if +// collision happens. It does not check whether labels are not sorted +func Test_createLabelSet(t *testing.T) { + tests := []struct { + name string + orig []common.StringKeyValue + externalLabels map[string]string + extras []string + want []prompb.Label + }{ + { + "labels_clean", + lbs1, + map[string]string{}, + []string{label31, value31, label32, value32}, + getPromLabels(label11, value11, label12, value12, label31, value31, label32, value32), + }, + { + "labels_duplicate_in_extras", + lbs1, + map[string]string{}, + []string{label11, value31}, + getPromLabels(label11, value31, label12, value12), + }, + { + "labels_dirty", + lbs1Dirty, + map[string]string{}, + []string{label31 + dirty1, value31, label32, value32}, + getPromLabels(label11+"_", value11, "key_"+label12, value12, label31+"_", value31, label32, value32), + }, + { + "no_original_case", + nil, + nil, + []string{label31, value31, label32, value32}, + getPromLabels(label31, value31, label32, value32), + }, + { + "empty_extra_case", + lbs1, + map[string]string{}, + []string{"", ""}, + getPromLabels(label11, value11, label12, value12, "", ""), + }, + { + "single_left_over_case", + lbs1, + map[string]string{}, + []string{label31, value31, label32}, + getPromLabels(label11, value11, label12, value12, label31, value31), + }, + { + "valid_external_labels", + lbs1, + exlbs1, + []string{label31, value31, label32, value32}, + getPromLabels(label11, value11, label12, value12, label41, value41, label31, value31, label32, value32), + }, + { + "overwritten_external_labels", + lbs1, + exlbs2, + []string{label31, value31, label32, value32}, + getPromLabels(label11, value11, label12, value12, label31, value31, label32, value32), + }, + } + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatch(t, tt.want, createLabelSet(tt.orig, tt.externalLabels, tt.extras...)) + }) + } +} + +// Tes_getPromMetricName checks if OTLP metric names are converted to Cortex metric names correctly. +// Test cases are empty namespace, monotonic metrics that require a total suffix, and metric names that contains +// invalid characters. +func Test_getPromMetricName(t *testing.T) { + tests := []struct { + name string + metric *otlp.Metric + ns string + want string + }{ + { + "nil_case", + nil, + ns1, + "", + }, + { + "normal_case", + validMetrics1[validDoubleGauge], + ns1, + "test_ns_" + validDoubleGauge, + }, + { + "empty_namespace", + validMetrics1[validDoubleGauge], + "", + validDoubleGauge, + }, + { + "total_suffix", + validMetrics1[validIntSum], + ns1, + "test_ns_" + validIntSum + delimeter + totalStr, + }, + { + "dirty_string", + validMetrics2[validIntGaugeDirty], + "7" + ns1, + "key_7test_ns__" + validIntGauge + "_", + }, + } + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, getPromMetricName(tt.metric, tt.ns)) + }) + } +} + +// Test_batchTimeSeries checks batchTimeSeries return the correct number of requests +// depending on byte size. +func Test_batchTimeSeries(t *testing.T) { + // First we will instantiate a dummy TimeSeries instance to pass into both the export call and compare the http request + labels := getPromLabels(label11, value11, label12, value12, label21, value21, label22, value22) + sample1 := getSample(floatVal1, msTime1) + sample2 := getSample(floatVal2, msTime2) + sample3 := getSample(floatVal3, msTime3) + ts1 := getTimeSeries(labels, sample1, sample2) + ts2 := getTimeSeries(labels, sample1, sample2, sample3) + + tsMap1 := getTimeseriesMap([]*prompb.TimeSeries{}) + tsMap2 := getTimeseriesMap([]*prompb.TimeSeries{ts1}) + tsMap3 := getTimeseriesMap([]*prompb.TimeSeries{ts1, ts2}) + + tests := []struct { + name string + tsMap map[string]*prompb.TimeSeries + maxBatchByteSize int + numExpectedRequests int + returnErr bool + }{ + { + "no_timeseries", + tsMap1, + 100, + -1, + true, + }, + { + "normal_case", + tsMap2, + 300, + 1, + false, + }, + { + "two_requests", + tsMap3, + 300, + 2, + false, + }, + } + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + requests, err := batchTimeSeries(tt.tsMap, tt.maxBatchByteSize) + if tt.returnErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.numExpectedRequests, len(requests)) + }) + } +} diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/img/cortex.png b/internal/otel_collector/exporter/prometheusremotewriteexporter/img/cortex.png new file mode 100644 index 0000000000000000000000000000000000000000..75769e61302c18a97c64ebf08785f578c2044390 GIT binary patch literal 73392 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

jml3=ICD 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 literal 0 HcmV?d00001 diff --git a/internal/otel_collector/exporter/prometheusremotewriteexporter/img/timeseries.png b/internal/otel_collector/exporter/prometheusremotewriteexporter/img/timeseries.png new file mode 100644 index 0000000000000000000000000000000000000000..54f406ce53fbe7dc6f7c7cbbb8af70b056da3704 GIT binary patch literal 9001 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 nRgvbOOB}`): What to name + services missing this information. + +Example: + +```yaml +exporters: + zipkin: + endpoint: "http://some.url:9411/api/v2/spans" + cert_file: file.cert + key_file: file.key + zipkin/2: + endpoint: "http://some.url:9411/api/v2/spans" + insecure: true +``` + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [HTTP settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/confighttp/README.md) +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) diff --git a/internal/otel_collector/exporter/zipkinexporter/config.go b/internal/otel_collector/exporter/zipkinexporter/config.go new file mode 100644 index 00000000000..280c31a932f --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/config.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +// Config defines configuration settings for the Zipkin exporter. +type Config struct { + configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + // Configures the exporter client. + // The Endpoint to send the Zipkin trace data to (e.g.: http://some.url:9411/api/v2/spans). + confighttp.HTTPClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + + Format string `mapstructure:"format"` + + DefaultServiceName string `mapstructure:"default_service_name"` +} diff --git a/internal/otel_collector/exporter/zipkinexporter/config_test.go b/internal/otel_collector/exporter/zipkinexporter/config_test.go new file mode 100644 index 00000000000..c242ac3366b --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/config_test.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "context" + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Exporters[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + e0 := cfg.Exporters["zipkin"] + + // URL doesn't have a default value so set it directly. + defaultCfg := factory.CreateDefaultConfig().(*Config) + defaultCfg.Endpoint = "http://some.location.org:9411/api/v2/spans" + assert.Equal(t, defaultCfg, e0) + assert.Equal(t, "json", e0.(*Config).Format) + + e1 := cfg.Exporters["zipkin/2"] + assert.Equal(t, &Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "zipkin/2", + TypeVal: "zipkin", + }, + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 10 * time.Second, + MaxInterval: 1 * time.Minute, + MaxElapsedTime: 10 * time.Minute, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 2, + QueueSize: 10, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://somedest:1234/api/v2/spans", + WriteBufferSize: 524288, + Timeout: 5 * time.Second, + }, + Format: "proto", + DefaultServiceName: "test_name", + }, e1) + params := component.ExporterCreateParams{Logger: zap.NewNop()} + _, err = factory.CreateTracesExporter(context.Background(), params, e1) + require.NoError(t, err) +} diff --git a/internal/otel_collector/exporter/zipkinexporter/factory.go b/internal/otel_collector/exporter/zipkinexporter/factory.go new file mode 100644 index 00000000000..daf5d1b5411 --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/factory.go @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "context" + "errors" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "zipkin" + + defaultTimeout = time.Second * 5 + + defaultFormat = "json" + + defaultServiceName string = "" +) + +// NewFactory creates a factory for Zipkin exporter. +func NewFactory() component.ExporterFactory { + return exporterhelper.NewFactory( + typeStr, + createDefaultConfig, + exporterhelper.WithTraces(createTraceExporter)) +} + +func createDefaultConfig() configmodels.Exporter { + return &Config{ + ExporterSettings: configmodels.ExporterSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + RetrySettings: exporterhelper.DefaultRetrySettings(), + QueueSettings: exporterhelper.DefaultQueueSettings(), + HTTPClientSettings: confighttp.HTTPClientSettings{ + Timeout: defaultTimeout, + // We almost read 0 bytes, so no need to tune ReadBufferSize. + WriteBufferSize: 512 * 1024, + }, + Format: defaultFormat, + DefaultServiceName: defaultServiceName, + } +} + +func createTraceExporter( + _ context.Context, + params component.ExporterCreateParams, + cfg configmodels.Exporter, +) (component.TracesExporter, error) { + zc := cfg.(*Config) + + if zc.Endpoint == "" { + // TODO https://github.com/open-telemetry/opentelemetry-collector/issues/215 + return nil, errors.New("exporter config requires a non-empty 'endpoint'") + } + + ze, err := createZipkinExporter(zc) + if err != nil { + return nil, err + } + return exporterhelper.NewTraceExporter( + zc, + params.Logger, + ze.pushTraceData, + // explicitly disable since we rely on http.Client timeout logic. + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithQueue(zc.QueueSettings), + exporterhelper.WithRetry(zc.RetrySettings)) +} diff --git a/internal/otel_collector/exporter/zipkinexporter/factory_test.go b/internal/otel_collector/exporter/zipkinexporter/factory_test.go new file mode 100644 index 00000000000..861287e333b --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/factory_test.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateInstanceViaFactory(t *testing.T) { + cfg := createDefaultConfig() + + // Default config doesn't have default endpoint so creating from it should + // fail. + ze, err := createTraceExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + assert.Error(t, err) + assert.Nil(t, ze) + + // URL doesn't have a default value so set it directly. + zeCfg := cfg.(*Config) + zeCfg.Endpoint = "http://some.location.org:9411/api/v2/spans" + ze, err = createTraceExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, cfg) + assert.NoError(t, err) + assert.NotNil(t, ze) +} diff --git a/internal/otel_collector/exporter/zipkinexporter/testdata/config.yaml b/internal/otel_collector/exporter/zipkinexporter/testdata/config.yaml new file mode 100644 index 00000000000..0a68945d00f --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/testdata/config.yaml @@ -0,0 +1,29 @@ +receivers: + examplereceiver: + +processors: + exampleprocessor: + +exporters: + zipkin: + endpoint: "http://some.location.org:9411/api/v2/spans" + zipkin/2: + endpoint: "https://somedest:1234/api/v2/spans" + format: proto + default_service_name: test_name + sending_queue: + enabled: true + num_consumers: 2 + queue_size: 10 + retry_on_failure: + enabled: true + initial_interval: 10s + max_interval: 60s + max_elapsed_time: 10m + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [zipkin, zipkin/2] diff --git a/internal/otel_collector/exporter/zipkinexporter/testutils_test.go b/internal/otel_collector/exporter/zipkinexporter/testutils_test.go new file mode 100644 index 00000000000..baee904b9ee --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/testutils_test.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "encoding/json" + "testing" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/stretchr/testify/require" +) + +func unmarshalZipkinSpanArrayToMap(t *testing.T, jsonStr string) map[zipkinmodel.ID]*zipkinmodel.SpanModel { + var i interface{} + + err := json.Unmarshal([]byte(jsonStr), &i) + require.NoError(t, err) + + results := make(map[zipkinmodel.ID]*zipkinmodel.SpanModel) + + switch x := i.(type) { + case []interface{}: + for _, j := range x { + span := jsonToSpan(t, j) + results[span.ID] = span + } + default: + span := jsonToSpan(t, x) + results[span.ID] = span + } + return results +} + +func jsonToSpan(t *testing.T, j interface{}) *zipkinmodel.SpanModel { + b, err := json.Marshal(j) + require.NoError(t, err) + span := &zipkinmodel.SpanModel{} + err = span.UnmarshalJSON(b) + require.NoError(t, err) + return span +} diff --git a/internal/otel_collector/exporter/zipkinexporter/zipkin.go b/internal/otel_collector/exporter/zipkinexporter/zipkin.go new file mode 100644 index 00000000000..8054a2244a7 --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/zipkin.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "bytes" + "context" + "fmt" + "net/http" + + "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" + zipkinreporter "github.com/openzipkin/zipkin-go/reporter" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/trace/zipkin" +) + +// zipkinExporter is a multiplexing exporter that spawns a new OpenCensus-Go Zipkin +// exporter per unique node encountered. This is because serviceNames per node define +// unique services, alongside their IPs. Also it is useful to receive traffic from +// Zipkin servers and then transform them back to the final form when creating an +// OpenCensus spandata. +type zipkinExporter struct { + defaultServiceName string + + url string + client *http.Client + serializer zipkinreporter.SpanSerializer +} + +func createZipkinExporter(cfg *Config) (*zipkinExporter, error) { + client, err := cfg.HTTPClientSettings.ToClient() + if err != nil { + return nil, err + } + + ze := &zipkinExporter{ + defaultServiceName: cfg.DefaultServiceName, + url: cfg.Endpoint, + client: client, + } + + switch cfg.Format { + case "json": + ze.serializer = zipkinreporter.JSONSerializer{} + case "proto": + ze.serializer = zipkin_proto3.SpanSerializer{} + default: + return nil, fmt.Errorf("%s is not one of json or proto", cfg.Format) + } + + return ze, nil +} + +func (ze *zipkinExporter) pushTraceData(ctx context.Context, td pdata.Traces) (int, error) { + tbatch, err := zipkin.InternalTracesToZipkinSpans(td) + if err != nil { + return td.SpanCount(), consumererror.Permanent(fmt.Errorf("failed to push trace data via Zipkin exporter: %w", err)) + } + + body, err := ze.serializer.Serialize(tbatch) + if err != nil { + return td.SpanCount(), consumererror.Permanent(fmt.Errorf("failed to push trace data via Zipkin exporter: %w", err)) + } + + req, err := http.NewRequestWithContext(ctx, "POST", ze.url, bytes.NewReader(body)) + if err != nil { + return td.SpanCount(), fmt.Errorf("failed to push trace data via Zipkin exporter: %w", err) + } + req.Header.Set("Content-Type", ze.serializer.ContentType()) + + resp, err := ze.client.Do(req) + if err != nil { + return td.SpanCount(), fmt.Errorf("failed to push trace data via Zipkin exporter: %w", err) + } + _ = resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return td.SpanCount(), fmt.Errorf("failed the request with status code %d", resp.StatusCode) + } + return 0, nil +} diff --git a/internal/otel_collector/exporter/zipkinexporter/zipkin_test.go b/internal/otel_collector/exporter/zipkinexporter/zipkin_test.go new file mode 100644 index 00000000000..f9d81c06793 --- /dev/null +++ b/internal/otel_collector/exporter/zipkinexporter/zipkin_test.go @@ -0,0 +1,359 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinexporter + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" + zipkinreporter "github.com/openzipkin/zipkin-go/reporter" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/receiver/zipkinreceiver" + "go.opentelemetry.io/collector/testutil" +) + +// This function tests that Zipkin spans that are received then processed roundtrip +// back to almost the same JSON with differences: +// a) Go's net.IP.String intentional shortens 0s with "::" but also converts to hex values +// so +// "7::0.128.128.127" +// becomes +// "7::80:807f" +// +// The rest of the fields should match up exactly +func TestZipkinExporter_roundtripJSON(t *testing.T) { + buf := new(bytes.Buffer) + var sizes []int64 + cst := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s, _ := io.Copy(buf, r.Body) + sizes = append(sizes, s) + r.Body.Close() + })) + defer cst.Close() + + config := &Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: cst.URL, + }, + Format: "json", + } + zexp, err := NewFactory().CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, config) + assert.NoError(t, err) + require.NotNil(t, zexp) + + // The test requires the spans from zipkinSpansJSONJavaLibrary to be sent in a single batch, use + // a mock to ensure that this happens as intended. + mzr := newMockZipkinReporter(cst.URL) + + // Run the Zipkin receiver to "receive spans upload from a client application" + addr := testutil.GetAvailableLocalAddress(t) + cfg := &zipkinreceiver.Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: "zipkin_receiver", + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: addr, + }, + } + zi, err := zipkinreceiver.New(cfg, zexp) + assert.NoError(t, err) + require.NotNil(t, zi) + + require.NoError(t, zi.Start(context.Background(), componenttest.NewNopHost())) + defer zi.Shutdown(context.Background()) + + // Let the receiver receive "uploaded Zipkin spans from a Java client application" + req, _ := http.NewRequest("POST", "https://tld.org/", strings.NewReader(zipkinSpansJSONJavaLibrary)) + responseWriter := httptest.NewRecorder() + zi.ServeHTTP(responseWriter, req) + + // Use the mock zipkin reporter to ensure all expected spans in a single batch. Since Flush waits for + // server response there is no need for further synchronization. + require.NoError(t, mzr.Flush()) + + // We expect back the exact JSON that was received + wants := []string{` + [{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385","parentId": "86154a4ba6e91385","id": "4d1e00c0db9010db", + "kind": "CLIENT","name": "get", + "timestamp": 1472470996199000,"duration": 207000, + "localEndpoint": {"serviceName": "frontend","ipv6": "7::80:807f"}, + "remoteEndpoint": {"serviceName": "backend","ipv4": "192.168.99.101","port": 9000}, + "annotations": [ + {"timestamp": 1472470996238000,"value": "foo"}, + {"timestamp": 1472470996403000,"value": "bar"} + ], + "tags": {"http.path": "/api","clnt/finagle.version": "6.45.0"} + }, + { + "traceId": "4d1e00c0db9010db86154a4ba6e91385","parentId": "86154a4ba6e91386","id": "4d1e00c0db9010dc", + "kind": "SERVER","name": "put", + "timestamp": 1472470996199000,"duration": 207000, + "localEndpoint": {"serviceName": "frontend","ipv6": "7::80:807f"}, + "remoteEndpoint": {"serviceName": "frontend", "ipv4": "192.168.99.101","port": 9000}, + "annotations": [ + {"timestamp": 1472470996238000,"value": "foo"}, + {"timestamp": 1472470996403000,"value": "bar"} + ], + "tags": {"http.path": "/api","clnt/finagle.version": "6.45.0"} + }, + { + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91386", + "id": "4d1e00c0db9010dd", + "kind": "SERVER", + "name": "put", + "timestamp": 1472470996199000, + "duration": 207000 + }] + `} + for i, s := range wants { + want := unmarshalZipkinSpanArrayToMap(t, s) + gotBytes := buf.Next(int(sizes[i])) + got := unmarshalZipkinSpanArrayToMap(t, string(gotBytes)) + for id, expected := range want { + actual, ok := got[id] + assert.True(t, ok) + assert.Equal(t, expected.ID, actual.ID) + assert.Equal(t, expected.Name, actual.Name) + assert.Equal(t, expected.TraceID, actual.TraceID) + assert.Equal(t, expected.Timestamp, actual.Timestamp) + assert.Equal(t, expected.Duration, actual.Duration) + assert.Equal(t, expected.Kind, actual.Kind) + } + } +} + +type mockZipkinReporter struct { + url string + client *http.Client + batch []*zipkinmodel.SpanModel + serializer zipkinreporter.SpanSerializer +} + +var _ zipkinreporter.Reporter = (*mockZipkinReporter)(nil) + +func (r *mockZipkinReporter) Send(span zipkinmodel.SpanModel) { + r.batch = append(r.batch, &span) +} +func (r *mockZipkinReporter) Close() error { + return nil +} + +func newMockZipkinReporter(url string) *mockZipkinReporter { + return &mockZipkinReporter{ + url: url, + client: &http.Client{}, + serializer: zipkinreporter.JSONSerializer{}, + } +} + +func (r *mockZipkinReporter) Flush() error { + sendBatch := r.batch + r.batch = nil + + if len(sendBatch) == 0 { + return nil + } + + body, err := r.serializer.Serialize(sendBatch) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", r.url, bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", r.serializer.ContentType()) + + resp, err := r.client.Do(req) + if err != nil { + return err + } + _ = resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return fmt.Errorf("http request failed with status code %d", resp.StatusCode) + } + + return nil +} + +const zipkinSpansJSONJavaLibrary = ` +[{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91386", + "id": "4d1e00c0db9010dc", + "kind": "SERVER", + "name": "put", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "frontend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91386", + "id": "4d1e00c0db9010dd", + "kind": "SERVER", + "name": "put", + "timestamp": 1472470996199000, + "duration": 207000 +}] +` + +func TestZipkinExporter_invalidFormat(t *testing.T) { + config := &Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "1.2.3.4", + }, + Format: "foobar", + } + f := NewFactory() + params := component.ExporterCreateParams{Logger: zap.NewNop()} + _, err := f.CreateTracesExporter(context.Background(), params, config) + require.Error(t, err) +} + +// The rest of the fields should match up exactly +func TestZipkinExporter_roundtripProto(t *testing.T) { + buf := new(bytes.Buffer) + var contentType string + cst := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.Copy(buf, r.Body) + contentType = r.Header.Get("Content-Type") + r.Body.Close() + })) + defer cst.Close() + + config := &Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: cst.URL, + }, + Format: "proto", + } + zexp, err := NewFactory().CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, config) + require.NoError(t, err) + + // The test requires the spans from zipkinSpansJSONJavaLibrary to be sent in a single batch, use + // a mock to ensure that this happens as intended. + mzr := newMockZipkinReporter(cst.URL) + + mzr.serializer = zipkin_proto3.SpanSerializer{} + + // Run the Zipkin receiver to "receive spans upload from a client application" + port := testutil.GetAvailablePort(t) + cfg := &zipkinreceiver.Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: "zipkin_receiver", + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: fmt.Sprintf(":%d", port), + }, + } + zi, err := zipkinreceiver.New(cfg, zexp) + require.NoError(t, err) + + err = zi.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err) + defer zi.Shutdown(context.Background()) + + // Let the receiver receive "uploaded Zipkin spans from a Java client application" + req, _ := http.NewRequest("POST", "https://tld.org/", strings.NewReader(zipkinSpansJSONJavaLibrary)) + responseWriter := httptest.NewRecorder() + zi.ServeHTTP(responseWriter, req) + + // Use the mock zipkin reporter to ensure all expected spans in a single batch. Since Flush waits for + // server response there is no need for further synchronization. + err = mzr.Flush() + require.NoError(t, err) + + require.Equal(t, zipkin_proto3.SpanSerializer{}.ContentType(), contentType) + // Finally we need to inspect the output + gotBytes, err := ioutil.ReadAll(buf) + require.NoError(t, err) + + _, err = zipkin_proto3.ParseSpans(gotBytes, false) + require.NoError(t, err) +} diff --git a/internal/otel_collector/extension/README.md b/internal/otel_collector/extension/README.md new file mode 100644 index 00000000000..8aaa8286049 --- /dev/null +++ b/internal/otel_collector/extension/README.md @@ -0,0 +1,107 @@ +# General Information + +Extensions provide capabilities on top of the primary functionality of the +collector. Generally, extensions are used for implementing components that can +be added to the Collector, but which do not require direct access to telemetry +data and are not part of the pipelines (like receivers, processors or +exporters). Example extensions are: Health Check extension that responds to +health check requests or PProf extension that allows fetching Collector's +performance profile. + +Supported service extensions (sorted alphabetically): + +- [Health Check](healthcheckextension/README.md) +- [Performance Profiler](pprofextension/README.md) +- [zPages](zpagesextension/README.md) + +The [contributors +repository](https://github.com/open-telemetry/opentelemetry-collector-contrib) +may have more extensions that can be added to custom builds of the Collector. + +## Ordering Extensions + +The order extensions are specified for the service is important as this is the +order in which each extension will be started and the reverse order in which they +will be shutdown. The ordering is determined in the `extensions` tag under the +`service` tag in the configuration file, example: + +```yaml +service: + # Extensions specified below are going to be loaded by the service in the + # order given below, and shutdown on reverse order. + extensions: [health_check, pprof, zpages] +``` + +# Extensions + +## Health Check +Health Check extension enables an HTTP url that can be probed to check the +status of the the OpenTelemetry Collector. This extension can be used as a +liveness and/or readiness probe on Kubernetes. + +The following settings are required: + +- `port` (default = 13133): What port to expose HTTP health information. + +Example: + +```yaml +extensions: + health_check: +``` + +The full list of settings exposed for this exporter is documented [here](healthcheckextension/config.go) +with detailed sample configurations [here](healthcheckextension/testdata/config.yaml). + +## Performance Profiler + +Performance Profiler extension enables the golang `net/http/pprof` endpoint. +This is typically used by developers to collect performance profiles and +investigate issues with the service. + +The following settings are required: + +- `endpoint` (default = localhost:1777): The endpoint in which the pprof will +be listening to. +- `block_profile_fraction` (default = 0): Fraction of blocking events that +are profiled. A value <= 0 disables profiling. See +https://golang.org/pkg/runtime/#SetBlockProfileRate for details. +- `mutex_profile_fraction` (default = 0): Fraction of mutex contention +events that are profiled. A value <= 0 disables profiling. See +https://golang.org/pkg/runtime/#SetMutexProfileFraction for details. + +The following settings can be optionally configured: + +- `save_to_file`: File name to save the CPU profile to. The profiling starts when the +Collector starts and is saved to the file when the Collector is terminated. + +Example: + +```yaml +extensions: + pprof: +``` + +The full list of settings exposed for this exporter are documented [here](pprofextension/config.go) +with detailed sample configurations [here](pprofextension/testdata/config.yaml). + +## zPages + +Enables an extension that serves zPages, an HTTP endpoint that provides live +data for debugging different components that were properly instrumented for such. +All core exporters and receivers provide some zPages instrumentation. + +The following settings are required: + +- `endpoint` (default = localhost:55679): Specifies the HTTP endpoint that serves +zPages. + +Example: + +```yaml +extensions: + zpages: +``` + +The full list of settings exposed for this exporter are documented [here](zpagesextension/config.go) +with detailed sample configurations [here](zpagesextension/testdata/config.yaml). diff --git a/internal/otel_collector/extension/extensionhelper/factory.go b/internal/otel_collector/extension/extensionhelper/factory.go new file mode 100644 index 00000000000..125901ba598 --- /dev/null +++ b/internal/otel_collector/extension/extensionhelper/factory.go @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensionhelper + +import ( + "context" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" +) + +// FactoryOption apply changes to ExporterOptions. +type FactoryOption func(o *factory) + +// CreateDefaultConfig is the equivalent of component.ExtensionFactory.CreateDefaultConfig() +type CreateDefaultConfig func() configmodels.Extension + +// CreateServiceExtension is the equivalent of component.ExtensionFactory.CreateExtension() +type CreateServiceExtension func(context.Context, component.ExtensionCreateParams, configmodels.Extension) (component.ServiceExtension, error) + +type factory struct { + cfgType configmodels.Type + customUnmarshaler component.CustomUnmarshaler + createDefaultConfig CreateDefaultConfig + createServiceExtension CreateServiceExtension +} + +// WithCustomUnmarshaler implements component.ConfigUnmarshaler. +func WithCustomUnmarshaler(customUnmarshaler component.CustomUnmarshaler) FactoryOption { + return func(o *factory) { + o.customUnmarshaler = customUnmarshaler + } +} + +// NewFactory returns a component.ExtensionFactory. +func NewFactory( + cfgType configmodels.Type, + createDefaultConfig CreateDefaultConfig, + createServiceExtension CreateServiceExtension, + options ...FactoryOption) component.ExtensionFactory { + f := &factory{ + cfgType: cfgType, + createDefaultConfig: createDefaultConfig, + createServiceExtension: createServiceExtension, + } + for _, opt := range options { + opt(f) + } + var ret component.ExtensionFactory + if f.customUnmarshaler != nil { + ret = &factoryWithUnmarshaler{f} + } else { + ret = f + } + return ret +} + +// Type gets the type of the Extension config created by this factory. +func (f *factory) Type() configmodels.Type { + return f.cfgType +} + +// CreateDefaultConfig creates the default configuration for processor. +func (f *factory) CreateDefaultConfig() configmodels.Extension { + return f.createDefaultConfig() +} + +// CreateExtension creates a component.TraceExtension based on this config. +func (f *factory) CreateExtension( + ctx context.Context, + params component.ExtensionCreateParams, + cfg configmodels.Extension) (component.ServiceExtension, error) { + return f.createServiceExtension(ctx, params, cfg) +} + +var _ component.ConfigUnmarshaler = (*factoryWithUnmarshaler)(nil) + +type factoryWithUnmarshaler struct { + *factory +} + +// Unmarshal un-marshals the config using the provided custom unmarshaler. +func (f *factoryWithUnmarshaler) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error { + return f.customUnmarshaler(componentViperSection, intoCfg) +} diff --git a/internal/otel_collector/extension/extensionhelper/factory_test.go b/internal/otel_collector/extension/extensionhelper/factory_test.go new file mode 100644 index 00000000000..b5d0f079ec1 --- /dev/null +++ b/internal/otel_collector/extension/extensionhelper/factory_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensionhelper + +import ( + "context" + "errors" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" +) + +const typeStr = "test" + +var ( + defaultCfg = &configmodels.ExtensionSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + nopExtensionInstance = new(nopExtension) +) + +func TestNewFactory(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig, + createExtension) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + ext, err := factory.CreateExtension(context.Background(), component.ExtensionCreateParams{}, defaultCfg) + assert.NoError(t, err) + assert.Same(t, nopExtensionInstance, ext) +} + +func TestNewFactory_WithConstructors(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig, + createExtension, + WithCustomUnmarshaler(customUnmarshaler)) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + + fu, ok := factory.(component.ConfigUnmarshaler) + assert.True(t, ok) + assert.Equal(t, errors.New("my error"), fu.Unmarshal(nil, nil)) + + ext, err := factory.CreateExtension(context.Background(), component.ExtensionCreateParams{}, defaultCfg) + assert.NoError(t, err) + assert.Same(t, nopExtensionInstance, ext) +} + +func defaultConfig() configmodels.Extension { + return defaultCfg +} + +func createExtension(context.Context, component.ExtensionCreateParams, configmodels.Extension) (component.ServiceExtension, error) { + return nopExtensionInstance, nil +} + +func customUnmarshaler(*viper.Viper, interface{}) error { + return errors.New("my error") +} + +type nopExtension struct { +} + +func (ne *nopExtension) Start(context.Context, component.Host) error { + return nil +} + +// Shutdown stops the exporter and is invoked during shutdown. +func (ne *nopExtension) Shutdown(context.Context) error { + return nil +} diff --git a/internal/otel_collector/extension/fluentbitextension/README.md b/internal/otel_collector/extension/fluentbitextension/README.md new file mode 100644 index 00000000000..27e43d4ba7c --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/README.md @@ -0,0 +1,65 @@ +# FluentBit Subprocess Extension + +**This extension is experimental and may receive breaking changes or be removed +at any time.** + +The `fluentbit` extension facilitates running a FluentBit subprocess of the +collector. This is meant to be used in conjunction with the `fluentforward` +receiver such that the FluentBit subprocess will be configured to send to the +TCP socket opened by the `fluentforward` receiver. This extension does not +actually listen for the logs from FluentBit, it just starts a FluentBit +subprocess that will generally send to a `fluentforward` receiver, which must +be configured separately. + +You are responsible for providing a configuration to FluentBit via the `config` +config option. This will be provided to the subprocess, along with a few other +config options to enhance the integration with the collector. + +**As of now, this extension is only targeted for Linux environments. It does not +work on Windows or MacOS.** + + +## Example Config + +```yaml +extensions: + health_check: + fluentbit: + executable_path: /usr/src/fluent-bit/build/bin/fluent-bit + tcp_endpoint: 127.0.0.1:8006 + config: | + [SERVICE] + parsers_file /usr/src/fluent-bit/conf/parsers.conf + [INPUT] + name tail + path /var/log/mylog + parser apache +receivers: + fluentforward: + endpoint: 0.0.0.0:8006 + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 1s + static_configs: + - targets: ['127.0.0.1:8888'] + # This will connect to the Fluent Bit subprocess's built-in HTTP + # monitoring server to grab Promtheus metrics. + - job_name: 'fluentbit' + scrape_interval: 1s + metrics_path: '/api/v1/metrics/prometheus' + static_configs: + - targets: ['127.0.0.1:2020'] +service: + pipelines: + logs: + receivers: [fluentforward] + processors: [] + exporters: [mylogsexporter] + metrics: + receivers: [prometheus] + processors: [batch] + exporters: [mymetricsexporter] + extensions: [health_check, zpages, fluentbit, pprof] +``` diff --git a/internal/otel_collector/extension/fluentbitextension/config.go b/internal/otel_collector/extension/fluentbitextension/config.go new file mode 100644 index 00000000000..5696c0f0553 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/config.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentbitextension + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config has the configuration for the fluentbit extension. +type Config struct { + configmodels.ExtensionSettings `mapstructure:",squash"` + + // The TCP `host:port` to which the subprocess should send log entries. + // This is required unless you are overridding `args` and providing the + // output configuration yourself either in `args` or `config`. + TCPEndpoint string `mapstructure:"tcp_endpoint"` + + // The path to the executable for FluentBit. Ideally should be an absolute + // path since the CWD of the collector is not guaranteed to be stable. + ExecutablePath string `mapstructure:"executable_path"` + + // Exec arguments to the FluentBit process. If you provide this, none of + // the standard args will be set, and only these provided args will be + // passed to FluentBit. The standard args will set the flush interval to 1 + // second, configure the forward output with the given `tcp_endpoint` + // option, enable the HTTP monitoring server in FluentBit, and set the + // config file to stdin. The only required arg is `--config=/dev/stdin`, + // since this extension passes the provided config to FluentBit via stdin. + // If you set args manually, you will be responsible for setting the + // forward output to the right port for the fluentforward receiver. See + // `process.go#constructArgs` of this extension source to see the current + // default args. + Args []string `mapstructure:"args"` + + // A configuration for FluentBit. This is the text content of the config + // itself, not a path to a config file. + Config string `mapstructure:"config"` +} diff --git a/internal/otel_collector/extension/fluentbitextension/config_test.go b/internal/otel_collector/extension/fluentbitextension/config_test.go new file mode 100644 index 00000000000..35d81d76766 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/config_test.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentbitextension + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Extensions[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + ext0 := cfg.Extensions["fluentbit"] + assert.Equal(t, factory.CreateDefaultConfig(), ext0) + + ext1 := cfg.Extensions["fluentbit/1"] + assert.Equal(t, + &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "fluentbit", + NameVal: "fluentbit/1", + }, + ExecutablePath: "/usr/local/bin/fluent-bit", + }, + ext1) + + assert.Equal(t, 1, len(cfg.Service.Extensions)) + assert.Equal(t, "fluentbit/1", cfg.Service.Extensions[0]) +} diff --git a/internal/otel_collector/extension/fluentbitextension/factory.go b/internal/otel_collector/extension/fluentbitextension/factory.go new file mode 100644 index 00000000000..e4a40adf147 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/factory.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentbitextension + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/extension/extensionhelper" +) + +const ( + // The value of extension "type" in configuration. + typeStr = "fluentbit" +) + +// NewFactory creates a factory for FluentBit extension. +func NewFactory() component.ExtensionFactory { + return extensionhelper.NewFactory( + typeStr, + createDefaultConfig, + createExtension) +} + +func createDefaultConfig() configmodels.Extension { + return &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createExtension(_ context.Context, params component.ExtensionCreateParams, cfg configmodels.Extension) (component.ServiceExtension, error) { + config := cfg.(*Config) + return newProcessManager(config, params.Logger), nil +} diff --git a/internal/otel_collector/extension/fluentbitextension/factory_test.go b/internal/otel_collector/extension/fluentbitextension/factory_test.go new file mode 100644 index 00000000000..0b22c0e8743 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/factory_test.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentbitextension + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" +) + +func TestFactory_CreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.Equal(t, &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + }, + cfg) + + assert.NoError(t, configcheck.ValidateConfig(cfg)) + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) +} + +func TestFactory_CreateExtension(t *testing.T) { + cfg := createDefaultConfig().(*Config) + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) +} diff --git a/internal/otel_collector/extension/fluentbitextension/process.go b/internal/otel_collector/extension/fluentbitextension/process.go new file mode 100644 index 00000000000..c16aa2bec08 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/process.go @@ -0,0 +1,224 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentbitextension + +import ( + "bufio" + "context" + "io" + "os" + "os/exec" + "syscall" + "time" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" +) + +type processManager struct { + cancel context.CancelFunc + conf *Config + logger *zap.Logger + shutdownSignal chan struct{} +} + +func newProcessManager(conf *Config, logger *zap.Logger) *processManager { + return &processManager{ + conf: conf, + logger: logger, + shutdownSignal: make(chan struct{}), + } +} + +type procState string + +// A global var that is available only for testing +var restartDelay = 10 * time.Second + +const ( + Starting procState = "starting" + Running procState = "running" + ShuttingDown procState = "shutting-down" + Stopped procState = "stopped" + Restarting procState = "restarting" + Errored procState = "errored" +) + +func constructArgs(tcpEndpoint string) []string { + return []string{ + "--config=/dev/stdin", + "--http", + "--port=2020", + "--flush=1", + "-o", "forward://" + tcpEndpoint, + "--match=*", + } +} + +func (pm *processManager) Start(ctx context.Context, _ component.Host) error { + childCtx, cancel := context.WithCancel(ctx) + pm.cancel = cancel + + args := pm.conf.Args + if len(args) == 0 { + args = constructArgs(pm.conf.TCPEndpoint) + } + go func() { + run(childCtx, pm.conf.ExecutablePath, args, pm.conf.Config, pm.logger) + close(pm.shutdownSignal) + }() + return nil +} + +// Shutdown is invoked during service shutdown. +func (pm *processManager) Shutdown(context.Context) error { + pm.cancel() + t := time.NewTimer(5 * time.Second) + + // Wait for either the FluentBit process to terminate or the timeout + // period, whichever comes first. + select { + case <-pm.shutdownSignal: + case <-t.C: + } + + return nil +} + +func run(ctx context.Context, execPath string, args []string, config string, logger *zap.Logger) { + state := Starting + + var cmd *exec.Cmd + var err error + var stdin io.WriteCloser + var stdout io.ReadCloser + // procWait is guaranteed to be sent exactly one message per successful process start + procWait := make(chan error) + + // A state machine makes the management easier to understand and account + // for all of the edge cases when managing a subprocess. + for { + logger.Debug("Fluent extension changed state", zap.String("state", string(state))) + + switch state { + case Errored: + logger.Error("FluentBit process died", zap.Error(err)) + state = Restarting + + case Starting: + cmd, stdin, stdout = createCommand(execPath, args) + + logger.Debug("Starting fluent subprocess", zap.String("command", cmd.String())) + err = cmd.Start() + if err != nil { + state = Errored + continue + } + + go signalWhenProcessDone(cmd, procWait) + + state = Running + + case Running: + go collectOutput(stdout, logger) + + err = renderConfig(config, stdin) + stdin.Close() + if err != nil { + state = Errored + continue + } + + select { + case err = <-procWait: + if ctx.Err() == nil { + // We aren't supposed to shutdown yet so this is an error + // state. + state = Errored + continue + } + state = Stopped + case <-ctx.Done(): + state = ShuttingDown + } + + case ShuttingDown: + _ = cmd.Process.Signal(syscall.SIGTERM) + <-procWait + stdout.Close() + state = Stopped + + case Restarting: + _ = stdout.Close() + _ = stdin.Close() + + // Sleep for a bit so we don't have a hot loop on repeated failures. + time.Sleep(restartDelay) + state = Starting + + case Stopped: + return + } + } +} + +func signalWhenProcessDone(cmd *exec.Cmd, procWait chan<- error) { + err := cmd.Wait() + procWait <- err +} + +func renderConfig(config string, writer io.Writer) error { + if config == "" { + return nil + } + + _, err := writer.Write([]byte(config)) + return err +} + +func createCommand(execPath string, args []string) (*exec.Cmd, io.WriteCloser, io.ReadCloser) { + cmd := exec.Command(execPath, args...) + + inReader, inWriter, err := os.Pipe() + if err != nil { + panic("Input pipe could not be created for subprocess") + } + + cmd.Stdin = inReader + + outReader, outWriter, err := os.Pipe() + // If this errors things are really wrong with the system + if err != nil { + panic("Output pipe could not be created for subprocess") + } + cmd.Stdout = outWriter + cmd.Stderr = outWriter + + cmd.Env = os.Environ() + + applyOSSpecificCmdModifications(cmd) + + return cmd, inWriter, outReader +} + +func collectOutput(stdout io.Reader, logger *zap.Logger) { + scanner := bufio.NewScanner(stdout) + + for scanner.Scan() { + logger.Debug(scanner.Text()) + } + // Returns when stdout is closed when the process ends +} diff --git a/internal/otel_collector/extension/fluentbitextension/process_linux.go b/internal/otel_collector/extension/fluentbitextension/process_linux.go new file mode 100644 index 00000000000..f5e945a7dd6 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/process_linux.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package fluentbitextension + +import ( + "os/exec" + "syscall" +) + +func applyOSSpecificCmdModifications(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + // This is Linux-specific and will cause the subprocess to be killed by the OS if + // the collector dies + Pdeathsig: syscall.SIGTERM, + } +} diff --git a/internal/otel_collector/extension/fluentbitextension/process_linux_test.go b/internal/otel_collector/extension/fluentbitextension/process_linux_test.go new file mode 100644 index 00000000000..5862f85f7bc --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/process_linux_test.go @@ -0,0 +1,177 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentbitextension + +import ( + "context" + "io/ioutil" + "os" + "strings" + "testing" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/shirou/gopsutil/process" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" +) + +const mockScript = `#!/bin/sh + +echo "Config:" 1>&2 +cat - + +sleep 100 + +` + +func setup(t *testing.T, conf *Config) (*processManager, **process.Process, func() bool, func()) { + logCore, logObserver := observer.New(zap.DebugLevel) + logger := zap.New(logCore) + + mockScriptFile, err := ioutil.TempFile("", "mocksubproc") + require.Nil(t, err) + + cleanup := func() { + spew.Dump(logObserver.All()) + os.Remove(mockScriptFile.Name()) + } + + _, err = mockScriptFile.Write([]byte(mockScript)) + require.Nil(t, err) + + err = mockScriptFile.Chmod(0700) + require.Nil(t, err) + + require.NoError(t, mockScriptFile.Close()) + + conf.ExecutablePath = mockScriptFile.Name() + pm := newProcessManager(conf, logger) + + var mockProc *process.Process + findSubproc := func() bool { + selfPid := os.Getpid() + procs, _ := process.Processes() + for _, proc := range procs { + if ppid, _ := proc.Ppid(); ppid == int32(selfPid) { + cmdline, _ := proc.Cmdline() + if strings.HasPrefix(cmdline, "/bin/sh "+mockScriptFile.Name()) { + mockProc = proc + return true + } + } + } + return false + } + + return pm, &mockProc, findSubproc, cleanup +} + +func TestProcessManager(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pm, mockProc, findSubproc, cleanup := setup(t, &Config{ + TCPEndpoint: "127.0.0.1:8000", + Config: "example config", + }) + defer cleanup() + + pm.Start(ctx, nil) + defer pm.Shutdown(ctx) + + require.Eventually(t, findSubproc, 12*time.Second, 100*time.Millisecond) + require.NotNil(t, *mockProc) + + cmdline, err := (*mockProc).Cmdline() + require.Nil(t, err) + require.Equal(t, + "/bin/sh "+pm.conf.ExecutablePath+ + " --config=/dev/stdin --http --port=2020 --flush=1 -o forward://127.0.0.1:8000 --match=*", + cmdline) + + oldProcPid := (*mockProc).Pid + err = (*mockProc).Kill() + require.NoError(t, err) + + // Should be restarted + require.Eventually(t, findSubproc, restartDelay+3*time.Second, 100*time.Millisecond) + require.NotNil(t, *mockProc) + + require.NotEqual(t, (*mockProc).Pid, oldProcPid) +} + +func TestProcessManagerArgs(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pm, mockProc, findSubproc, cleanup := setup(t, &Config{ + TCPEndpoint: "127.0.0.1:8000", + Config: "example config", + Args: []string{"--http"}, + }) + defer cleanup() + + pm.Start(ctx, nil) + defer pm.Shutdown(ctx) + + require.Eventually(t, findSubproc, 12*time.Second, 100*time.Millisecond) + require.NotNil(t, *mockProc) + + cmdline, err := (*mockProc).Cmdline() + require.Nil(t, err) + require.Equal(t, + "/bin/sh "+pm.conf.ExecutablePath+ + " --http", + cmdline) +} + +func TestProcessManagerBadExec(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logCore, logObserver := observer.New(zap.DebugLevel) + logger := zap.New(logCore) + + pm := newProcessManager(&Config{ + ExecutablePath: "/does/not/exist", + TCPEndpoint: "127.0.0.1:8000", + Config: "example config", + }, logger) + + pm.Start(ctx, nil) + defer pm.Shutdown(ctx) + + time.Sleep(restartDelay + 2*time.Second) + require.Len(t, logObserver.FilterMessage("FluentBit process died").All(), 2) +} + +func TestProcessManagerEmptyConfig(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pm, mockProc, findSubproc, cleanup := setup(t, &Config{ + TCPEndpoint: "127.0.0.1:8000", + Config: "", + }) + defer cleanup() + + pm.Start(ctx, nil) + defer pm.Shutdown(ctx) + + require.Eventually(t, findSubproc, 15*time.Second, 100*time.Millisecond) + require.NotNil(t, *mockProc) +} diff --git a/internal/otel_collector/extension/fluentbitextension/process_others.go b/internal/otel_collector/extension/fluentbitextension/process_others.go new file mode 100644 index 00000000000..f6edff2faba --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/process_others.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux + +package fluentbitextension + +import ( + "os/exec" +) + +func applyOSSpecificCmdModifications(_ *exec.Cmd) {} diff --git a/internal/otel_collector/extension/fluentbitextension/testdata/config.yaml b/internal/otel_collector/extension/fluentbitextension/testdata/config.yaml new file mode 100644 index 00000000000..a7ff30fbe48 --- /dev/null +++ b/internal/otel_collector/extension/fluentbitextension/testdata/config.yaml @@ -0,0 +1,20 @@ +extensions: + fluentbit: + fluentbit/1: + executable_path: /usr/local/bin/fluent-bit + +service: + extensions: [fluentbit/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + +# Data pipeline is required to load the config. +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: diff --git a/internal/otel_collector/extension/healthcheckextension/README.md b/internal/otel_collector/extension/healthcheckextension/README.md new file mode 100644 index 00000000000..a57ab881dae --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/README.md @@ -0,0 +1,19 @@ +# Health Check + +Health Check extension enables an HTTP url that can be probed to check the +status of the the OpenTelemetry Collector. This extension can be used as a +liveness and/or readiness probe on Kubernetes. + +The following settings are required: + +- `port` (default = 13133): What port to expose HTTP health information. + +Example: + +```yaml +extensions: + health_check: +``` + +The full list of settings exposed for this exporter is documented [here](./config.go) +with detailed sample configurations [here](./testdata/config.yaml). diff --git a/internal/otel_collector/extension/healthcheckextension/config.go b/internal/otel_collector/extension/healthcheckextension/config.go new file mode 100644 index 00000000000..9369260a01b --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/config.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package healthcheckextension + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config has the configuration for the extension enabling the health check +// extension, used to report the health status of the service. +type Config struct { + configmodels.ExtensionSettings `mapstructure:",squash"` + + // Port is the port used to publish the health check status. + // The default value is 13133. + Port uint16 `mapstructure:"port"` +} diff --git a/internal/otel_collector/extension/healthcheckextension/config_test.go b/internal/otel_collector/extension/healthcheckextension/config_test.go new file mode 100644 index 00000000000..76c3c2c4272 --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/config_test.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package healthcheckextension + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Extensions[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + ext0 := cfg.Extensions["health_check"] + assert.Equal(t, factory.CreateDefaultConfig(), ext0) + + ext1 := cfg.Extensions["health_check/1"] + assert.Equal(t, + &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "health_check", + NameVal: "health_check/1", + }, + Port: 13, + }, + ext1) + + assert.Equal(t, 1, len(cfg.Service.Extensions)) + assert.Equal(t, "health_check/1", cfg.Service.Extensions[0]) +} diff --git a/internal/otel_collector/extension/healthcheckextension/doc.go b/internal/otel_collector/extension/healthcheckextension/doc.go new file mode 100644 index 00000000000..21f44479696 --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/doc.go @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package healthcheckextension implements an extension that enables an HTTP +// endpoint that can be used to check the overall health and status of the +// service. +package healthcheckextension diff --git a/internal/otel_collector/extension/healthcheckextension/factory.go b/internal/otel_collector/extension/healthcheckextension/factory.go new file mode 100644 index 00000000000..6d29f92d338 --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/factory.go @@ -0,0 +1,73 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package healthcheckextension + +import ( + "context" + "errors" + "sync/atomic" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/extension/extensionhelper" +) + +const ( + // The value of extension "type" in configuration. + typeStr = "health_check" +) + +// NewFactory creates a factory for HealthCheck extension. +func NewFactory() component.ExtensionFactory { + return extensionhelper.NewFactory( + typeStr, + createDefaultConfig, + createExtension) +} + +func createDefaultConfig() configmodels.Extension { + return &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Port: 13133, + } +} + +func createExtension(_ context.Context, params component.ExtensionCreateParams, cfg configmodels.Extension) (component.ServiceExtension, error) { + config := cfg.(*Config) + + // The runtime settings are global to the application, so while in principle it + // is possible to have more than one instance, running multiple does not bring + // any value to the service. + // In order to avoid this issue we will allow the creation of a single + // instance once per process while keeping the private function that allow + // the creation of multiple instances for unit tests. Summary: only a single + // instance can be created via the factory. + if !atomic.CompareAndSwapInt32(&instanceState, instanceNotCreated, instanceCreated) { + return nil, errors.New("only a single health check extension instance can be created per process") + } + + return newServer(*config, params.Logger), nil +} + +// See comment in createExtension how these are used. +var instanceState int32 + +const ( + instanceNotCreated int32 = 0 + instanceCreated int32 = 1 +) diff --git a/internal/otel_collector/extension/healthcheckextension/factory_test.go b/internal/otel_collector/extension/healthcheckextension/factory_test.go new file mode 100644 index 00000000000..a23b433fb5e --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/factory_test.go @@ -0,0 +1,78 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package healthcheckextension + +import ( + "context" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/testutil" +) + +func TestFactory_CreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.Equal(t, &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + Port: 13133, + }, + cfg) + + assert.NoError(t, configcheck.ValidateConfig(cfg)) + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} + +func TestFactory_CreateExtension(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Port = testutil.GetAvailablePort(t) + + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} + +func TestFactory_CreateExtensionOnlyOnce(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Port = testutil.GetAvailablePort(t) + + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + ext1, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.Error(t, err) + require.Nil(t, ext1) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} diff --git a/internal/otel_collector/extension/healthcheckextension/healthcheckextension.go b/internal/otel_collector/extension/healthcheckextension/healthcheckextension.go new file mode 100644 index 00000000000..29e568f4301 --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/healthcheckextension.go @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package healthcheckextension + +import ( + "context" + "net" + "net/http" + "strconv" + + "github.com/jaegertracing/jaeger/pkg/healthcheck" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" +) + +type healthCheckExtension struct { + config Config + logger *zap.Logger + state *healthcheck.HealthCheck + server http.Server +} + +var _ component.PipelineWatcher = (*healthCheckExtension)(nil) + +func (hc *healthCheckExtension) Start(_ context.Context, host component.Host) error { + + hc.logger.Info("Starting health_check extension", zap.Any("config", hc.config)) + + // Initialize listener + portStr := ":" + strconv.Itoa(int(hc.config.Port)) + ln, err := net.Listen("tcp", portStr) + if err != nil { + host.ReportFatalError(err) + return nil + } + + // Mount HC handler + hc.server.Handler = hc.state.Handler() + + go func() { + // The listener ownership goes to the server. + if err := hc.server.Serve(ln); err != http.ErrServerClosed && err != nil { + host.ReportFatalError(err) + } + }() + + return nil +} + +func (hc *healthCheckExtension) Shutdown(context.Context) error { + return hc.server.Close() +} + +func (hc *healthCheckExtension) Ready() error { + hc.state.Set(healthcheck.Ready) + return nil +} + +func (hc *healthCheckExtension) NotReady() error { + hc.state.Set(healthcheck.Unavailable) + return nil +} + +func newServer(config Config, logger *zap.Logger) *healthCheckExtension { + hc := &healthCheckExtension{ + config: config, + logger: logger, + state: healthcheck.New(), + server: http.Server{}, + } + + hc.state.SetLogger(logger) + + return hc +} diff --git a/internal/otel_collector/extension/healthcheckextension/healthcheckextension_test.go b/internal/otel_collector/extension/healthcheckextension/healthcheckextension_test.go new file mode 100644 index 00000000000..603756e1f3b --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/healthcheckextension_test.go @@ -0,0 +1,142 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package healthcheckextension + +import ( + "context" + "net" + "net/http" + "runtime" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/testutil" +) + +func TestHealthCheckExtensionUsage(t *testing.T) { + config := Config{ + Port: testutil.GetAvailablePort(t), + } + + hcExt := newServer(config, zap.NewNop()) + require.NotNil(t, hcExt) + + require.NoError(t, hcExt.Start(context.Background(), componenttest.NewNopHost())) + defer hcExt.Shutdown(context.Background()) + + // Give a chance for the server goroutine to run. + runtime.Gosched() + + client := &http.Client{} + url := "http://localhost:" + strconv.Itoa(int(config.Port)) + resp0, err := client.Get(url) + require.NoError(t, err) + defer resp0.Body.Close() + + require.Equal(t, http.StatusServiceUnavailable, resp0.StatusCode) + + hcExt.Ready() + resp1, err := client.Get(url) + require.NoError(t, err) + defer resp1.Body.Close() + require.Equal(t, http.StatusOK, resp1.StatusCode) + + hcExt.NotReady() + resp2, err := client.Get(url) + require.NoError(t, err) + defer resp2.Body.Close() + require.Equal(t, http.StatusServiceUnavailable, resp2.StatusCode) +} + +func TestHealthCheckExtensionPortAlreadyInUse(t *testing.T) { + endpoint := testutil.GetAvailableLocalAddress(t) + _, portStr, err := net.SplitHostPort(endpoint) + require.NoError(t, err) + + // This needs to be ":port" because health checks also tries to connect to ":port". + // To avoid the pop-up "accept incoming network connections" health check should be changed + // to accept an address. + ln, err := net.Listen("tcp", ":"+portStr) + require.NoError(t, err) + defer ln.Close() + + port, err := strconv.Atoi(portStr) + require.NoError(t, err) + + config := Config{ + Port: uint16(port), + } + hcExt := newServer(config, zap.NewNop()) + require.NotNil(t, hcExt) + + // Health check will report port already in use in a goroutine, use the error waiting + // host to get it. + mh := componenttest.NewErrorWaitingHost() + require.NoError(t, hcExt.Start(context.Background(), mh)) + + receivedError, receivedErr := mh.WaitForFatalError(500 * time.Millisecond) + require.True(t, receivedError) + require.Error(t, receivedErr) +} + +func TestHealthCheckMultipleStarts(t *testing.T) { + config := Config{ + Port: testutil.GetAvailablePort(t), + } + + hcExt := newServer(config, zap.NewNop()) + require.NotNil(t, hcExt) + + mh := componenttest.NewErrorWaitingHost() + require.NoError(t, hcExt.Start(context.Background(), mh)) + defer hcExt.Shutdown(context.Background()) + + // Health check will report already in use in a goroutine, use the error waiting + // host to get it. + require.NoError(t, hcExt.Start(context.Background(), mh)) + + receivedError, receivedErr := mh.WaitForFatalError(500 * time.Millisecond) + require.True(t, receivedError) + require.Error(t, receivedErr) +} + +func TestHealthCheckMultipleShutdowns(t *testing.T) { + config := Config{ + Port: testutil.GetAvailablePort(t), + } + + hcExt := newServer(config, zap.NewNop()) + require.NotNil(t, hcExt) + + require.NoError(t, hcExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, hcExt.Shutdown(context.Background())) + require.NoError(t, hcExt.Shutdown(context.Background())) +} + +func TestHealthCheckShutdownWithoutStart(t *testing.T) { + config := Config{ + Port: testutil.GetAvailablePort(t), + } + + hcExt := newServer(config, zap.NewNop()) + require.NotNil(t, hcExt) + + require.NoError(t, hcExt.Shutdown(context.Background())) +} diff --git a/internal/otel_collector/extension/healthcheckextension/testdata/config.yaml b/internal/otel_collector/extension/healthcheckextension/testdata/config.yaml new file mode 100644 index 00000000000..79b7d9758c1 --- /dev/null +++ b/internal/otel_collector/extension/healthcheckextension/testdata/config.yaml @@ -0,0 +1,20 @@ +extensions: + health_check: + health_check/1: + port: 13 + +service: + extensions: [health_check/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + +# Data pipeline is required to load the config. +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: diff --git a/internal/otel_collector/extension/pprofextension/README.md b/internal/otel_collector/extension/pprofextension/README.md new file mode 100644 index 00000000000..d69bcf1bc76 --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/README.md @@ -0,0 +1,32 @@ +# Performance Profiler + +Performance Profiler extension enables the golang `net/http/pprof` endpoint. +This is typically used by developers to collect performance profiles and +investigate issues with the service. + +The following settings are required: + +- `endpoint` (default = localhost:1777): The endpoint in which the pprof will +be listening to. Use localhost: to make it available only locally, or +":" to make it available on all network interfaces. +- `block_profile_fraction` (default = 0): Fraction of blocking events that +are profiled. A value <= 0 disables profiling. See +https://golang.org/pkg/runtime/#SetBlockProfileRate for details. +- `mutex_profile_fraction` (default = 0): Fraction of mutex contention +events that are profiled. A value <= 0 disables profiling. See +https://golang.org/pkg/runtime/#SetMutexProfileFraction for details. + +The following settings can be optionally configured: + +- `save_to_file`: File name to save the CPU profile to. The profiling starts when the +Collector starts and is saved to the file when the Collector is terminated. + +Example: +```yaml + +extensions: + pprof: +``` + +The full list of settings exposed for this exporter are documented [here](./config.go) +with detailed sample configurations [here](./testdata/config.yaml). diff --git a/internal/otel_collector/extension/pprofextension/config.go b/internal/otel_collector/extension/pprofextension/config.go new file mode 100644 index 00000000000..0eea6de88c1 --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/config.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pprofextension + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config has the configuration for the extension enabling the golang +// net/http/pprof (Performance Profiler) extension. +type Config struct { + configmodels.ExtensionSettings `mapstructure:",squash"` + + // Endpoint is the address and port in which the pprof will be listening to. + // Use localhost: to make it available only locally, or ":" to + // make it available on all network interfaces. + Endpoint string `mapstructure:"endpoint"` + + // Fraction of blocking events that are profiled. A value <= 0 disables + // profiling. See https://golang.org/pkg/runtime/#SetBlockProfileRate for details. + BlockProfileFraction int `mapstructure:"block_profile_fraction"` + + // Fraction of mutex contention events that are profiled. A value <= 0 + // disables profiling. See https://golang.org/pkg/runtime/#SetMutexProfileFraction + // for details. + MutexProfileFraction int `mapstructure:"mutex_profile_fraction"` + + // Optional file name to save the CPU profile to. The profiling starts when the + // Collector starts and is saved to the file when the Collector is terminated. + SaveToFile string `mapstructure:"save_to_file"` +} diff --git a/internal/otel_collector/extension/pprofextension/config_test.go b/internal/otel_collector/extension/pprofextension/config_test.go new file mode 100644 index 00000000000..a0fe6df4a59 --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/config_test.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pprofextension + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Extensions[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + ext0 := cfg.Extensions["pprof"] + assert.Equal(t, factory.CreateDefaultConfig(), ext0) + + ext1 := cfg.Extensions["pprof/1"] + assert.Equal(t, + &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "pprof", + NameVal: "pprof/1", + }, + Endpoint: "0.0.0.0:1777", + BlockProfileFraction: 3, + MutexProfileFraction: 5, + }, + ext1) + + assert.Equal(t, 1, len(cfg.Service.Extensions)) + assert.Equal(t, "pprof/1", cfg.Service.Extensions[0]) +} diff --git a/internal/otel_collector/extension/pprofextension/doc.go b/internal/otel_collector/extension/pprofextension/doc.go new file mode 100644 index 00000000000..a16e8b7624e --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pprofextension implements an extension that exposes the golang +// net/http/pprof (Performance Profiler) in a HTTP endpoint. +package pprofextension diff --git a/internal/otel_collector/extension/pprofextension/factory.go b/internal/otel_collector/extension/pprofextension/factory.go new file mode 100644 index 00000000000..c6166dca0be --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/factory.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pprofextension + +import ( + "context" + "errors" + "sync/atomic" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/extension/extensionhelper" +) + +const ( + // The value of extension "type" in configuration. + typeStr = "pprof" +) + +// NewFactory creates a factory for pprof extension. +func NewFactory() component.ExtensionFactory { + return extensionhelper.NewFactory( + typeStr, + createDefaultConfig, + createExtension) +} + +func createDefaultConfig() configmodels.Extension { + return &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Endpoint: "localhost:1777", + } +} + +func createExtension(_ context.Context, params component.ExtensionCreateParams, cfg configmodels.Extension) (component.ServiceExtension, error) { + config := cfg.(*Config) + if config.Endpoint == "" { + return nil, errors.New("\"endpoint\" is required when using the \"pprof\" extension") + } + + // The runtime settings are global to the application, so while in principle it + // is possible to have more than one instance, running multiple will mean that + // the settings of the last started instance will prevail. In order to avoid + // this issue we will allow the creation of a single instance once per process + // while keeping the private function that allow the creation of multiple + // instances for unit tests. Summary: only a single instance can be created + // via the factory. + // TODO: Move this as an option to extensionhelper. + if !atomic.CompareAndSwapInt32(&instanceState, instanceNotCreated, instanceCreated) { + return nil, errors.New("only a single pprof extension instance can be created per process") + } + + return newServer(*config, params.Logger), nil +} + +// See comment in createExtension how these are used. +var instanceState int32 + +const ( + instanceNotCreated int32 = 0 + instanceCreated int32 = 1 +) diff --git a/internal/otel_collector/extension/pprofextension/factory_test.go b/internal/otel_collector/extension/pprofextension/factory_test.go new file mode 100644 index 00000000000..5ed29bf8ca8 --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/factory_test.go @@ -0,0 +1,78 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pprofextension + +import ( + "context" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/testutil" +) + +func TestFactory_CreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.Equal(t, &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + Endpoint: "localhost:1777", + }, + cfg) + + assert.NoError(t, configcheck.ValidateConfig(cfg)) + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} + +func TestFactory_CreateExtension(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Endpoint = testutil.GetAvailableLocalAddress(t) + + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} + +func TestFactory_CreateExtensionOnlyOnce(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Endpoint = testutil.GetAvailableLocalAddress(t) + + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + ext1, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.Error(t, err) + require.Nil(t, ext1) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} diff --git a/internal/otel_collector/extension/pprofextension/pprofextension.go b/internal/otel_collector/extension/pprofextension/pprofextension.go new file mode 100644 index 00000000000..bb9db577c76 --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/pprofextension.go @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pprofextension + +import ( + "context" + "net" + "net/http" + _ "net/http/pprof" // #nosec Needed to enable the performance profiler + "os" + "runtime" + "runtime/pprof" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" +) + +type pprofExtension struct { + config Config + logger *zap.Logger + server http.Server +} + +func (p *pprofExtension) Start(_ context.Context, host component.Host) error { + // Start the listener here so we can have earlier failure if port is + // already in use. + ln, err := net.Listen("tcp", p.config.Endpoint) + if err != nil { + return err + } + + runtime.SetBlockProfileRate(p.config.BlockProfileFraction) + runtime.SetMutexProfileFraction(p.config.MutexProfileFraction) + + p.logger.Info("Starting net/http/pprof server", zap.Any("config", p.config)) + go func() { + // The listener ownership goes to the server. + if err := p.server.Serve(ln); err != nil && err != http.ErrServerClosed { + host.ReportFatalError(err) + } + }() + + if p.config.SaveToFile != "" { + f, err := os.Create(p.config.SaveToFile) + if err != nil { + return err + } + return pprof.StartCPUProfile(f) + } + + return nil +} + +func (p *pprofExtension) Shutdown(context.Context) error { + if p.config.SaveToFile != "" { + pprof.StopCPUProfile() + } + return p.server.Close() +} + +func newServer(config Config, logger *zap.Logger) *pprofExtension { + return &pprofExtension{ + config: config, + logger: logger, + } +} diff --git a/internal/otel_collector/extension/pprofextension/pprofextension_test.go b/internal/otel_collector/extension/pprofextension/pprofextension_test.go new file mode 100644 index 00000000000..460fe6983e4 --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/pprofextension_test.go @@ -0,0 +1,110 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pprofextension + +import ( + "context" + "net" + "net/http" + "runtime" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/testutil" +) + +func TestPerformanceProfilerExtensionUsage(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + BlockProfileFraction: 3, + MutexProfileFraction: 5, + } + + pprofExt := newServer(config, zap.NewNop()) + require.NotNil(t, pprofExt) + + require.NoError(t, pprofExt.Start(context.Background(), componenttest.NewNopHost())) + defer pprofExt.Shutdown(context.Background()) + + // Give a chance for the server goroutine to run. + runtime.Gosched() + + _, pprofPort, err := net.SplitHostPort(config.Endpoint) + require.NoError(t, err) + + client := &http.Client{} + resp, err := client.Get("http://localhost:" + pprofPort + "/debug/pprof") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestPerformanceProfilerExtensionPortAlreadyInUse(t *testing.T) { + endpoint := testutil.GetAvailableLocalAddress(t) + ln, err := net.Listen("tcp", endpoint) + require.NoError(t, err) + defer ln.Close() + + config := Config{ + Endpoint: endpoint, + } + pprofExt := newServer(config, zap.NewNop()) + require.NotNil(t, pprofExt) + + require.Error(t, pprofExt.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestPerformanceProfilerMultipleStarts(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + pprofExt := newServer(config, zap.NewNop()) + require.NotNil(t, pprofExt) + + require.NoError(t, pprofExt.Start(context.Background(), componenttest.NewNopHost())) + defer pprofExt.Shutdown(context.Background()) + + // Try to start it again, it will fail since it is on the same endpoint. + require.Error(t, pprofExt.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestPerformanceProfilerMultipleShutdowns(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + pprofExt := newServer(config, zap.NewNop()) + require.NotNil(t, pprofExt) + + require.NoError(t, pprofExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, pprofExt.Shutdown(context.Background())) + require.NoError(t, pprofExt.Shutdown(context.Background())) +} + +func TestPerformanceProfilerShutdownWithoutStart(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + pprofExt := newServer(config, zap.NewNop()) + require.NotNil(t, pprofExt) + + require.NoError(t, pprofExt.Shutdown(context.Background())) +} diff --git a/internal/otel_collector/extension/pprofextension/testdata/config.yaml b/internal/otel_collector/extension/pprofextension/testdata/config.yaml new file mode 100644 index 00000000000..9b332d8fa6c --- /dev/null +++ b/internal/otel_collector/extension/pprofextension/testdata/config.yaml @@ -0,0 +1,22 @@ +extensions: + pprof: + pprof/1: + endpoint: "0.0.0.0:1777" + block_profile_fraction: 3 + mutex_profile_fraction: 5 + +service: + extensions: [pprof/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + +# Data pipeline is required to load the config. +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: diff --git a/internal/otel_collector/extension/zpagesextension/README.md b/internal/otel_collector/extension/zpagesextension/README.md new file mode 100644 index 00000000000..338e1d6f838 --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/README.md @@ -0,0 +1,20 @@ +# zPages + +Enables an extension that serves zPages, an HTTP endpoint that provides live +data for debugging different components that were properly instrumented for such. +All core exporters and receivers provide some zPage instrumentation. + +The following settings are required: + +- `endpoint` (default = localhost:55679): Specifies the HTTP endpoint that serves +zPages. Use localhost: to make it available only locally, or ":" to +make it available on all network interfaces. + +Example: +```yaml +extensions: + zpages: +``` + +The full list of settings exposed for this exporter are documented [here](./config.go) +with detailed sample configurations [here](./testdata/config.yaml). diff --git a/internal/otel_collector/extension/zpagesextension/config.go b/internal/otel_collector/extension/zpagesextension/config.go new file mode 100644 index 00000000000..2449ca139d2 --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/config.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zpagesextension + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config has the configuration for the extension enabling the zPages extension. +type Config struct { + configmodels.ExtensionSettings `mapstructure:",squash"` + + // Endpoint is the address and port in which the zPages will be listening to. + // Use localhost: to make it available only locally, or ":" to + // make it available on all network interfaces. + Endpoint string `mapstructure:"endpoint"` +} diff --git a/internal/otel_collector/extension/zpagesextension/config_test.go b/internal/otel_collector/extension/zpagesextension/config_test.go new file mode 100644 index 00000000000..ada938baca1 --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/config_test.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zpagesextension + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Extensions[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + ext0 := cfg.Extensions["zpages"] + assert.Equal(t, factory.CreateDefaultConfig(), ext0) + + ext1 := cfg.Extensions["zpages/1"] + assert.Equal(t, + &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "zpages", + NameVal: "zpages/1", + }, + Endpoint: "localhost:56888", + }, + ext1) + + assert.Equal(t, 1, len(cfg.Service.Extensions)) + assert.Equal(t, "zpages/1", cfg.Service.Extensions[0]) +} diff --git a/internal/otel_collector/extension/zpagesextension/doc.go b/internal/otel_collector/extension/zpagesextension/doc.go new file mode 100644 index 00000000000..7b312a5e9d2 --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package zpagesextension implements an extension that exposes zPages of +// properly instrumented components. +package zpagesextension diff --git a/internal/otel_collector/extension/zpagesextension/factory.go b/internal/otel_collector/extension/zpagesextension/factory.go new file mode 100644 index 00000000000..035a23f1e62 --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/factory.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zpagesextension + +import ( + "context" + "errors" + "sync/atomic" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/extension/extensionhelper" +) + +const ( + // The value of extension "type" in configuration. + typeStr = "zpages" +) + +// NewFactory creates a factory for Z-Pages extension. +func NewFactory() component.ExtensionFactory { + return extensionhelper.NewFactory( + typeStr, + createDefaultConfig, + createExtension) +} + +func createDefaultConfig() configmodels.Extension { + return &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Endpoint: "localhost:55679", + } +} + +// createExtension creates the extension based on this config. +func createExtension(_ context.Context, params component.ExtensionCreateParams, cfg configmodels.Extension) (component.ServiceExtension, error) { + config := cfg.(*Config) + if config.Endpoint == "" { + return nil, errors.New("\"endpoint\" is required when using the \"zpages\" extension") + } + + // The runtime settings are global to the application, so while in principle it + // is possible to have more than one instance, running multiple does not bring + // any value to the service. + // In order to avoid this issue we will allow the creation of a single + // instance once per process while keeping the private function that allow + // the creation of multiple instances for unit tests. Summary: only a single + // instance can be created via the factory. + if !atomic.CompareAndSwapInt32(&instanceState, instanceNotCreated, instanceCreated) { + return nil, errors.New("only a single zpages extension instance can be created per process") + } + + return newServer(*config, params.Logger), nil +} + +// See comment in CreateExtension how these are used. +var instanceState int32 + +const ( + instanceNotCreated int32 = 0 + instanceCreated int32 = 1 +) diff --git a/internal/otel_collector/extension/zpagesextension/factory_test.go b/internal/otel_collector/extension/zpagesextension/factory_test.go new file mode 100644 index 00000000000..ff5425a2c6a --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/factory_test.go @@ -0,0 +1,78 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zpagesextension + +import ( + "context" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/testutil" +) + +func TestFactory_CreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.Equal(t, &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + Endpoint: "localhost:55679", + }, + cfg) + + assert.NoError(t, configcheck.ValidateConfig(cfg)) + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} + +func TestFactory_CreateExtension(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Endpoint = testutil.GetAvailableLocalAddress(t) + + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} + +func TestFactory_CreateExtensionOnlyOnce(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Endpoint = testutil.GetAvailableLocalAddress(t) + + ext, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.NoError(t, err) + require.NotNil(t, ext) + + ext1, err := createExtension(context.Background(), component.ExtensionCreateParams{Logger: zap.NewNop()}, cfg) + require.Error(t, err) + require.Nil(t, ext1) + + // Restore instance tracking from factory, for other tests. + atomic.StoreInt32(&instanceState, instanceNotCreated) +} diff --git a/internal/otel_collector/extension/zpagesextension/testdata/config.yaml b/internal/otel_collector/extension/zpagesextension/testdata/config.yaml new file mode 100644 index 00000000000..6819540571b --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/testdata/config.yaml @@ -0,0 +1,20 @@ +extensions: + zpages: + zpages/1: + endpoint: "localhost:56888" + +service: + extensions: [zpages/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + +# Data pipeline is required to load the config. +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: diff --git a/internal/otel_collector/extension/zpagesextension/zpagesextension.go b/internal/otel_collector/extension/zpagesextension/zpagesextension.go new file mode 100644 index 00000000000..98eeb50ac99 --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/zpagesextension.go @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zpagesextension + +import ( + "context" + "net" + "net/http" + + "go.opencensus.io/zpages" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" +) + +type zpagesExtension struct { + config Config + logger *zap.Logger + server http.Server +} + +func (zpe *zpagesExtension) Start(_ context.Context, host component.Host) error { + zPagesMux := http.NewServeMux() + zpages.Handle(zPagesMux, "/debug") + + hostZPages, ok := host.(interface { + RegisterZPages(mux *http.ServeMux, pathPrefix string) + }) + if ok { + zpe.logger.Info("Register Host's zPages") + hostZPages.RegisterZPages(zPagesMux, "/debug") + } else { + zpe.logger.Info("Host's zPages not available") + } + + // Start the listener here so we can have earlier failure if port is + // already in use. + ln, err := net.Listen("tcp", zpe.config.Endpoint) + if err != nil { + return err + } + + zpe.logger.Info("Starting zPages extension", zap.Any("config", zpe.config)) + zpe.server = http.Server{Handler: zPagesMux} + go func() { + if err := zpe.server.Serve(ln); err != nil && err != http.ErrServerClosed { + host.ReportFatalError(err) + } + }() + + return nil +} + +func (zpe *zpagesExtension) Shutdown(context.Context) error { + return zpe.server.Close() +} + +func newServer(config Config, logger *zap.Logger) *zpagesExtension { + return &zpagesExtension{ + config: config, + logger: logger, + } +} diff --git a/internal/otel_collector/extension/zpagesextension/zpagesextension_test.go b/internal/otel_collector/extension/zpagesextension/zpagesextension_test.go new file mode 100644 index 00000000000..8ebb575357c --- /dev/null +++ b/internal/otel_collector/extension/zpagesextension/zpagesextension_test.go @@ -0,0 +1,108 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zpagesextension + +import ( + "context" + "net" + "net/http" + "runtime" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/testutil" +) + +func TestZPagesExtensionUsage(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + zpagesExt := newServer(config, zap.NewNop()) + require.NotNil(t, zpagesExt) + + require.NoError(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost())) + defer zpagesExt.Shutdown(context.Background()) + + // Give a chance for the server goroutine to run. + runtime.Gosched() + + _, zpagesPort, err := net.SplitHostPort(config.Endpoint) + require.NoError(t, err) + + client := &http.Client{} + resp, err := client.Get("http://localhost:" + zpagesPort + "/debug/tracez") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestZPagesExtensionPortAlreadyInUse(t *testing.T) { + endpoint := testutil.GetAvailableLocalAddress(t) + ln, err := net.Listen("tcp", endpoint) + require.NoError(t, err) + defer ln.Close() + + config := Config{ + Endpoint: endpoint, + } + zpagesExt := newServer(config, zap.NewNop()) + require.NotNil(t, zpagesExt) + + require.Error(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestZPagesMultipleStarts(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + zpagesExt := newServer(config, zap.NewNop()) + require.NotNil(t, zpagesExt) + + require.NoError(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost())) + defer zpagesExt.Shutdown(context.Background()) + + // Try to start it again, it will fail since it is on the same endpoint. + require.Error(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestZPagesMultipleShutdowns(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + zpagesExt := newServer(config, zap.NewNop()) + require.NotNil(t, zpagesExt) + + require.NoError(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, zpagesExt.Shutdown(context.Background())) + require.NoError(t, zpagesExt.Shutdown(context.Background())) +} + +func TestZPagesShutdownWithoutStart(t *testing.T) { + config := Config{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + zpagesExt := newServer(config, zap.NewNop()) + require.NotNil(t, zpagesExt) + + require.NoError(t, zpagesExt.Shutdown(context.Background())) +} diff --git a/internal/otel_collector/go.mod b/internal/otel_collector/go.mod new file mode 100644 index 00000000000..626c6c2c898 --- /dev/null +++ b/internal/otel_collector/go.mod @@ -0,0 +1,64 @@ +module go.opentelemetry.io/collector + +go 1.14 + +require ( + contrib.go.opencensus.io/exporter/prometheus v0.2.0 + github.com/OneOfOne/xxhash v1.2.5 // indirect + github.com/Shopify/sarama v1.27.2 + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/antonmedv/expr v1.8.9 + github.com/apache/thrift v0.13.0 + github.com/cenkalti/backoff v2.2.1+incompatible + github.com/census-instrumentation/opencensus-proto v0.3.0 + github.com/coreos/go-oidc v2.2.1+incompatible + github.com/davecgh/go-spew v1.1.1 + github.com/go-kit/kit v0.10.0 + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/gogo/googleapis v1.3.0 // indirect + github.com/gogo/protobuf v1.3.1 + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e + github.com/golang/protobuf v1.4.3 + github.com/golang/snappy v0.0.2 + github.com/google/go-cmp v0.5.4 + github.com/google/uuid v1.1.2 + github.com/gorilla/mux v1.8.0 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/jaegertracing/jaeger v1.21.0 + github.com/leoluk/perflib_exporter v0.1.0 + github.com/mattn/go-colorable v0.1.7 // indirect + github.com/mitchellh/mapstructure v1.3.2 // indirect + github.com/onsi/ginkgo v1.14.1 // indirect + github.com/onsi/gomega v1.10.2 // indirect + github.com/openzipkin/zipkin-go v0.2.5 + github.com/orijtech/prometheus-go-metrics-exporter v0.0.6 + github.com/pelletier/go-toml v1.8.0 // indirect + github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect + github.com/prometheus/client_golang v1.8.0 + github.com/prometheus/common v0.15.0 + github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4 + github.com/rs/cors v1.7.0 + github.com/shirou/gopsutil v3.20.11+incompatible + github.com/soheilhy/cmux v0.1.4 + github.com/spf13/cast v1.3.1 + github.com/spf13/cobra v1.1.1 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.6.1 + github.com/tinylib/msgp v1.1.5 + github.com/uber/jaeger-lib v2.4.0+incompatible + go.opencensus.io v0.22.5 + go.uber.org/atomic v1.7.0 + go.uber.org/zap v1.16.0 + golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 + golang.org/x/text v0.3.4 + google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d + google.golang.org/grpc v1.34.0 + google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9 // indirect + google.golang.org/protobuf v1.25.0 + gopkg.in/ini.v1 v1.57.0 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gopkg.in/yaml.v2 v2.4.0 + honnef.co/go/tools v0.0.1-2020.1.6 // indirect +) diff --git a/internal/otel_collector/go.sum b/internal/otel_collector/go.sum new file mode 100644 index 00000000000..52d75b5b662 --- /dev/null +++ b/internal/otel_collector/go.sum @@ -0,0 +1,1480 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +contrib.go.opencensus.io/exporter/prometheus v0.2.0 h1:9PUk0/8V0LGoPqVCrf8fQZJkFGBxudu8jOjQSMwoD6w= +contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/azure-sdk-for-go v46.4.0+incompatible h1:fCN6Pi+tEiEwFa8RSmtVlFHRXEZ+DJm9gfx/MKqYWw4= +github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.10 h1:j5sGbX7uj1ieYYkQ3Mpvewd4DCsEQ+ZeJpqnSM9pjnM= +github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/HdrHistogram/hdrhistogram-go v0.9.0 h1:dpujRju0R4M/QZzcnR1LH1qm+TVG3UzkWdp5tH1WMcg= +github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= +github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.22.2-0.20190604114437-cd910a683f9f/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= +github.com/Shopify/sarama v1.27.2 h1:1EyY1dsxNDUQEv0O/4TsjosHI2CgB1uo9H/v56xzTxc= +github.com/Shopify/sarama v1.27.2/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antonmedv/expr v1.8.9 h1:O9stiHmHHww9b4ozhPx7T6BK7fXfOCHJ8ybxf0833zw= +github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.35.5 h1:doSEOxC0UkirPcle20Rc+1kAhJ4Ip+GSEeZ3nKl7Qlk= +github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bsm/sarama-cluster v2.1.13+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digitalocean/godo v1.46.0 h1:WRbwjATilgz2NE4NGMeSDpeicy9h4xSKNGuRJ/Nq/fA= +github.com/digitalocean/godo v1.46.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible h1:+mzU0jHyjWpYHiD0StRlsVXkCvecWS2hc55M3OlUJSk= +github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.7.3/go.mod h1:V1d2J5pfxYH6EjBAgSK7YNXcXlTWxUHdE1sVDXkjnig= +github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk= +github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.3.0 h1:M695OaDJ5ipWvDPcoAg/YL9c3uORAegkEfBqTQF/fTQ= +github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201007051231-1066cbb265c7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gophercloud/gophercloud v0.13.0 h1:1XkslZZRm6Ks0bLup+hBNth+KQf+0JA1UeoB7YKw9E8= +github.com/gophercloud/gophercloud v0.13.0/go.mod h1:VX0Ibx85B60B5XOrZr6kaNwrmPUzcmMpwxvQ1WQIIWM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.5/go.mod h1:UJ0EZAp832vCd54Wev9N1BMKEyvcZ5+IM0AwDrnlkEc= +github.com/grpc-ecosystem/grpc-gateway v1.15.0/go.mod h1:vO11I9oWA+KsxmfFQPhLnnIb1VDE24M+pdxZFiuZcA8= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.7.0 h1:tGs8Oep67r8CcA2Ycmb/8BLBcJ70St44mF2X10a/qPg= +github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw= +github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.0 h1:1X+ga+2ki9aUJJaliI2isvjNLI8rNCGHFkZ1FaVpvCA= +github.com/hashicorp/go-hclog v0.14.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8= +github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM= +github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hetznercloud/hcloud-go v1.22.0 h1:CC0jwkaBzwP4ObFE0sdJBTvGh5DE9kB/tuDETnRfOik= +github.com/hetznercloud/hcloud-go v1.22.0/go.mod h1:xng8lbDUg+xM1dgc0yGHX5EeqbwIq7UYlMWMTx3SQVg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/jaegertracing/jaeger v1.21.0 h1:Fgre3vTI5E/cmkXKBXK7ksnzul5b/3gXjA3mQzt0+58= +github.com/jaegertracing/jaeger v1.21.0/go.mod h1:PCTGGFohQBPQMR4j333V5lt6If7tj8aWJ+pQNgvZ+wU= +github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg= +github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leoluk/perflib_exporter v0.1.0 h1:fXe/mDaf9jR+Zk8FjFlcCSksACuIj2VNN4GyKHmQqtA= +github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olivere/elastic v6.2.27+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing-contrib/go-grpc v0.0.0-20191001143057-db30781987df/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/orijtech/prometheus-go-metrics-exporter v0.0.6 h1:ExkpQsyDDcyp0U3zhoNUQaCQ/o0Ovq7e1jRCL9lQ/4o= +github.com/orijtech/prometheus-go-metrics-exporter v0.0.6/go.mod h1:BiTx/ugZex8LheBk3j53tktWaRdFjV5FCfT2o0P7msE= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f h1:JDEmUDtyiLMyMlFwiaDOv2hxUp35497fkwePcLeV7j4= +github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= +github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4 h1:54z99l8Q3TuyyeoNZkyY4Lq7eFht9J2Mynq4T1Hxbzc= +github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4/go.mod h1:XYjkJiog7fyQu3puQNivZPI2pNq1C/775EIoHfDvuvY= +github.com/prometheus/statsd_exporter v0.15.0 h1:UiwC1L5HkxEPeapXdm2Ye0u1vUJfTj7uwT5yydYpa1E= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e h1:CGjiMQ0wMH4wtNWrlj6kiTbkPt2F3rbYnhGX6TWLfco= +github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.1.5 h1:2gXmtWueD2HefZHQe1QOy9HVzmFrLOVvsXwXBQ0ayy0= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= +github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= +github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5/go.mod h1:ppEjwdhyy7Y31EnHRDm1JkChoC7LXIJ7Ex0VYLWtZtQ= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181112210238-4b1f3b6b1646/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200203023011-6f24f261dadb/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200603131246-cc40288be839/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201008025239-9df69603baec/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200624020401-64a14ca9d1ad/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9 h1:f+/+gfZ/tfaHBXXiv1gWRmCej6wlX3mLY4bnLpI99wk= +google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9/go.mod h1:5j1uub0jRGhRiSghIlrThmBUgcgLXOVJQ/l1getT4uo= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc= +honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= +k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= +k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.3.0 h1:WmkrnW7fdrm0/DMClc+HIxtftvxVIPAhlVwMQo5yLco= +k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/otel_collector/internal/buildscripts/gen-certs.sh b/internal/otel_collector/internal/buildscripts/gen-certs.sh new file mode 100644 index 00000000000..e5266367bf2 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/gen-certs.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +# This script is used to create the CA, server and client's certificates and keys required by unit tests. +# These certificates use the Subject Alternative Name extension rather than the Common Name, which will be unsupported from Go 1.15. + +usage() { + echo "Usage: $0 [-d]" + echo + echo "-d Dry-run mode. No project files will not be modified. Default: 'false'" + echo "-m Domain name to use in the certificate. Default: 'localhost'" + echo "-o Output directory where certificates will be written to. Default: '.'; the current directory" + exit 1 +} + +dry_run=false +domain="localhost" +output_dir="." + +while getopts "dm:o:" o; do + case "${o}" in + d) + dry_run=true + ;; + m) + domain=$OPTARG + ;; + o) + output_dir=$OPTARG + ;; + *) + usage + ;; + esac +done +shift $((OPTIND-1)) + +set -ex + +# Create temp dir for generated files. +tmp_dir=$(mktemp -d -t certificates) +clean_up() { + ARG=$? + if [ $dry_run = true ]; then + echo "Dry-run complete. Generated files can be found in $tmp_dir" + else + rm -rf "$tmp_dir" + fi + exit $ARG +} +trap clean_up EXIT + +gen_ssl_conf() { + domain_name=$1 + output_file=$2 + + cat << EOF > "$output_file" +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_distinguished_name +req_extensions = req_ext + +[ req_distinguished_name ] +countryName = AU +stateOrProvinceName = Australia +localityName = Sydney +organizationName = MyOrgName +commonName = MyCommonName + +[ req_ext ] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = $domain_name +EOF +} + +# Generate config files. +gen_ssl_conf "$domain" "$tmp_dir/ssl.conf" + +# Create CA (accept defaults from prompts). +openssl genrsa -out "$tmp_dir/ca.key" 2048 +openssl req -new -key "$tmp_dir/ca.key" -x509 -days 3650 -out "$tmp_dir/ca.crt" -config "$tmp_dir/ssl.conf" + +# Create client and server keys. +openssl genrsa -out "$tmp_dir/server.key" 2048 +openssl genrsa -out "$tmp_dir/client.key" 2048 + +# Create certificate sign request using the above created keys. +openssl req -new -nodes -key "$tmp_dir/server.key" -out "$tmp_dir/server.csr" -config "$tmp_dir/ssl.conf" +openssl req -new -nodes -key "$tmp_dir/client.key" -out "$tmp_dir/client.csr" -config "$tmp_dir/ssl.conf" + +# Creating the client and server certificates. +openssl x509 -req \ + -sha256 \ + -days 3650 \ + -in "$tmp_dir/server.csr" \ + -signkey "$tmp_dir/server.key" \ + -out "$tmp_dir/server.crt" \ + -extensions req_ext \ + -CA "$tmp_dir/ca.crt" \ + -CAkey "$tmp_dir/ca.key" \ + -CAcreateserial \ + -extfile "$tmp_dir/ssl.conf" +openssl x509 -req \ + -sha256 \ + -days 3650 \ + -in "$tmp_dir/client.csr" \ + -signkey "$tmp_dir/client.key" \ + -out "$tmp_dir/client.crt" \ + -extensions req_ext \ + -CA "$tmp_dir/ca.crt" \ + -CAkey "$tmp_dir/ca.key" \ + -CAcreateserial \ + -extfile "$tmp_dir/ssl.conf" + +# Copy files if not in dry-run mode. +if [ $dry_run = false ]; then + cp "$tmp_dir/ca.crt" \ + "$tmp_dir/client.crt" \ + "$tmp_dir/client.key" \ + "$tmp_dir/server.crt" \ + "$tmp_dir/server.key" \ + "$output_dir" +fi diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/Dockerfile b/internal/otel_collector/internal/buildscripts/packaging/fpm/Dockerfile new file mode 100644 index 00000000000..ec3ff295ff0 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/Dockerfile @@ -0,0 +1,16 @@ +FROM debian:9 + +RUN apt-get update && \ + apt-get install -y ruby ruby-dev rubygems build-essential git rpm + +RUN gem install --no-document fpm -v 1.11.0 + +VOLUME /repo +WORKDIR /repo + +ENV PACKAGE="deb" +ENV VERSION="" +ENV ARCH="amd64" +ENV OUTPUT_DIR="/repo/dist/" + +CMD ./internal/buildscripts/packaging/fpm/$PACKAGE/build.sh "$VERSION" "$ARCH" "$OUTPUT_DIR" \ No newline at end of file diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/common.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/common.sh new file mode 100644 index 00000000000..c87e6f454f5 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/common.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FPM_DIR="$( cd "$( dirname ${BASH_SOURCE[0]} )" && pwd )" + +PKG_NAME="otel-collector" +PKG_VENDOR="OpenTelemetry Community" +PKG_MAINTAINER="OpenTelemetry Community " +PKG_DESCRIPTION="OpenTelemetry Collector" +PKG_LICENSE="Apache 2.0" +PKG_URL="https://github.com/open-telemetry/opentelemetry-collector" +PKG_USER="otel" +PKG_GROUP="otel" + +SERVICE_NAME="otel-collector" +PROCESS_NAME="otelcol" + +SERVICE_PATH="$FPM_DIR/$SERVICE_NAME.service" +PREINSTALL_PATH="$FPM_DIR/preinstall.sh" +POSTINSTALL_PATH="$FPM_DIR/postinstall.sh" +PREUNINSTALL_PATH="$FPM_DIR/preuninstall.sh" + +install_pkg() { + local pkg_path="$1" + local pkg_base=$( basename "$pkg_path" ) + + echo "Installing $pkg_base ..." + docker cp "$pkg_path" $image_name:/tmp/$pkg_base + if [[ "${pkg_base##*.}" = "deb" ]]; then + $docker_exec dpkg -i /tmp/$pkg_base + else + $docker_exec rpm -ivh /tmp/$pkg_base + fi +} + +uninstall_pkg() { + local pkg_type="$1" + local pkg_name="${2:-"$PKG_NAME"}" + + echo "Uninstalling $pkg_name ..." + if [[ "$pkg_type" = "deb" ]]; then + $docker_exec dpkg -r $pkg_name + else + $docker_exec rpm -e $pkg_name + fi +} diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/deb/README.md b/internal/otel_collector/internal/buildscripts/packaging/fpm/deb/README.md new file mode 100644 index 00000000000..a23aa433e55 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/deb/README.md @@ -0,0 +1,13 @@ +# Build otel-collector deb package + +Build the otel-collector deb package with [fpm](https://github.com/jordansissel/fpm). + +To build the deb package, run `make deb-package` from the repo root directory. The deb package will be written to +`dist/otel-collector__.deb`. + +By default, `` is `amd64` and `` is the latest git tag with `-post` appended, e.g. `1.2.3-post`. +To override these defaults, set the `ARCH` and `VERSION` environment variables, e.g. +`ARCH=arm64 VERSION=4.5.6 make deb-package`. + +Run `./internal/buildscripts/packaging/fpm/test.sh PATH_TO_DEB_FILE` to run a basic installation test with the built +package. \ No newline at end of file diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/deb/build.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/deb/build.sh new file mode 100644 index 00000000000..db62ffb363b --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/deb/build.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euxo pipefail + +SCRIPT_DIR="$( cd "$( dirname ${BASH_SOURCE[0]} )" && pwd )" +REPO_DIR="$( cd "$SCRIPT_DIR/../../../../../" && pwd )" +VERSION="${1:-}" +ARCH="${2:-"amd64"}" +OUTPUT_DIR="${3:-"$REPO_DIR/dist/"}" +OTELCOL_PATH="$REPO_DIR/bin/otelcol_linux_$ARCH" +CONFIG_PATH="$REPO_DIR/examples/local/otel-config.yaml" + +mkdir -p $OUTPUT_DIR + +. $SCRIPT_DIR/../common.sh + +if [[ -z "$VERSION" ]]; then + latest_tag="$( git describe --abbrev=0 --match v[0-9]* )" + VERSION="${latest_tag}-post" +fi + +fpm -s dir -t deb -n $PKG_NAME -v ${VERSION#v} -f -p "$OUTPUT_DIR" \ + --vendor "$PKG_VENDOR" \ + --maintainer "$PKG_MAINTAINER" \ + --description "$PKG_DESCRIPTION" \ + --license "$PKG_LICENSE" \ + --url "$PKG_URL" \ + --architecture "$ARCH" \ + --config-files /etc/otel-collector/config.yaml \ + --deb-dist "stable" \ + --deb-user "$PKG_USER" \ + --deb-group "$PKG_GROUP" \ + --before-install "$PREINSTALL_PATH" \ + --after-install "$POSTINSTALL_PATH" \ + --pre-uninstall "$PREUNINSTALL_PATH" \ + $OTELCOL_PATH=/usr/bin/$PROCESS_NAME \ + $SERVICE_PATH=/lib/systemd/system/$SERVICE_NAME.service \ + $CONFIG_PATH=/etc/otel-collector/config.yaml diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/otel-collector.service b/internal/otel_collector/internal/buildscripts/packaging/fpm/otel-collector.service new file mode 100644 index 00000000000..faca2be7ce1 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/otel-collector.service @@ -0,0 +1,14 @@ +[Unit] +Description=OpenTelemety Collector +After=network.target + +[Service] +ExecStart=/usr/bin/otelcol --config /etc/otel-collector/config.yaml +KillMode=mixed +Restart=on-failure +Type=simple +User=otel +Group=otel + +[Install] +WantedBy=multi-user.target diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/postinstall.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/postinstall.sh new file mode 100644 index 00000000000..a7b39f0c5fd --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/postinstall.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if command -v systemctl >/dev/null 2>&1; then + systemctl enable otel-collector.service + if [ -f /etc/otel-collector/config.yaml ]; then + systemctl start otel-collector.service + fi +fi diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/preinstall.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/preinstall.sh new file mode 100644 index 00000000000..e90d69fa961 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/preinstall.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +getent passwd otel >/dev/null || useradd --system --user-group --no-create-home --shell /sbin/nologin otel diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/preuninstall.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/preuninstall.sh new file mode 100644 index 00000000000..b0a1bdded02 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/preuninstall.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if command -v systemctl >/dev/null 2>&1; then + systemctl stop otel-collector.service + systemctl disable otel-collector.service +fi diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/README.md b/internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/README.md new file mode 100644 index 00000000000..a7cb37530e9 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/README.md @@ -0,0 +1,13 @@ +# Build otel-collector rpm package + +Build the otel-collector rpm package with [fpm](https://github.com/jordansissel/fpm). + +To build the rpm package, run `make rpm-package` from the repo root directory. The rpm package will be written to +`dist/otel-collector-..rpm`. + +By default, `` is `amd64` and `` is the latest git tag with `~post` appended, e.g. `1.2.3~post`. +To override these defaults, set the `ARCH` and `VERSION` environment variables, e.g. +`ARCH=arm64 VERSION=4.5.6 make rpm-package`. + +Run `./internal/buildscripts/packaging/fpm/test.sh PATH_TO_RPM_FILE` to run a basic installation test with the built +package. \ No newline at end of file diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/build.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/build.sh new file mode 100644 index 00000000000..b07339df02d --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/rpm/build.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euxo pipefail + +SCRIPT_DIR="$( cd "$( dirname ${BASH_SOURCE[0]} )" && pwd )" +REPO_DIR="$( cd "$SCRIPT_DIR/../../../../../" && pwd )" +VERSION="${1:-}" +ARCH="${2:-"amd64"}" +OUTPUT_DIR="${3:-"$REPO_DIR/dist/"}" +OTELCOL_PATH="$REPO_DIR/bin/otelcol_linux_$ARCH" +CONFIG_PATH="$REPO_DIR/examples/local/otel-config.yaml" + +mkdir -p $OUTPUT_DIR + +. $SCRIPT_DIR/../common.sh + +if [[ -z "$VERSION" ]]; then + latest_tag="$( git describe --abbrev=0 --match v[0-9]* )" + VERSION="${latest_tag}~post" +fi + +fpm -s dir -t rpm -n $PKG_NAME -v ${VERSION#v} -f -p "$OUTPUT_DIR" \ + --vendor "$PKG_VENDOR" \ + --maintainer "$PKG_MAINTAINER" \ + --description "$PKG_DESCRIPTION" \ + --license "$PKG_LICENSE" \ + --url "$PKG_URL" \ + --architecture "$ARCH" \ + --config-files /etc/otel-collector/config.yaml \ + --rpm-summary "$PKG_DESCRIPTION" \ + --rpm-user "$PKG_USER" \ + --rpm-group "$PKG_GROUP" \ + --before-install "$PREINSTALL_PATH" \ + --after-install "$POSTINSTALL_PATH" \ + --pre-uninstall "$PREUNINSTALL_PATH" \ + $OTELCOL_PATH=/usr/bin/$PROCESS_NAME \ + $SERVICE_PATH=/lib/systemd/system/$SERVICE_NAME.service \ + $CONFIG_PATH=/etc/otel-collector/config.yaml diff --git a/internal/otel_collector/internal/buildscripts/packaging/fpm/test.sh b/internal/otel_collector/internal/buildscripts/packaging/fpm/test.sh new file mode 100644 index 00000000000..9dcc9622655 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/fpm/test.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname ${BASH_SOURCE[0]} )" && pwd )" +REPO_DIR="$( cd "$SCRIPT_DIR/../../../../" && pwd )" +PKG_PATH=${1:-} + +. $SCRIPT_DIR/common.sh + +if [[ -z "$PKG_PATH" ]]; then + echo "usage: ${BASH_SOURCE[0]} DEB_OR_RPM_PATH" >&2 + exit 1 +fi + +if [[ ! -f "$PKG_PATH" ]]; then + echo "$PKG_PATH not found!" >&2 + exit 1 +fi + +pkg_name="$( basename "$PKG_PATH" )" +pkg_type="${pkg_name##*.}" +if [[ ! "$pkg_type" =~ ^(deb|rpm)$ ]]; then + echo "$PKG_PATH not supported!" >&2 + exit 1 +fi +image_name="otelcol-$pkg_type-test" +container_name="$image_name" +docker_run="docker run --name $container_name -d -v /sys/fs/cgroup:/sys/fs/cgroup:ro --privileged $image_name" +docker_exec="docker exec $container_name" + +trap "docker rm -fv $container_name >/dev/null 2>&1 || true" EXIT + +docker build -t $image_name -f "$SCRIPT_DIR/$pkg_type/Dockerfile.test" "$SCRIPT_DIR" +docker rm -fv $container_name >/dev/null 2>&1 || true + +# test install +echo +$docker_run +install_pkg "$PKG_PATH" + +# ensure service has started and still running after 5 seconds +sleep 5 +echo "Checking $SERVICE_NAME service status ..." +$docker_exec systemctl --no-pager status $SERVICE_NAME + +echo "Checking $PROCESS_NAME process ..." +$docker_exec pgrep -a -u otel $PROCESS_NAME + +# test uninstall +echo +uninstall_pkg $pkg_type + +echo "Checking $SERVICE_NAME service status after uninstall ..." +if $docker_exec systemctl --no-pager status $SERVICE_NAME; then + echo "$SERVICE_NAME service still running after uninstall!" >&2 + exit 1 +fi +echo "$SERVICE_NAME service successfully stopped after uninstall" + +echo "Checking $SERVICE_NAME service existence after uninstall ..." +if $docker_exec systemctl list-unit-files --all | grep $SERVICE_NAME; then + echo "$SERVICE_NAME service still exists after uninstall!" >&2 + exit 1 +fi +echo "$SERVICE_NAME service successfully removed after uninstall" + +echo "Checking $PROCESS_NAME process after uninstall ..." +if $docker_exec pgrep $PROCESS_NAME; then + echo "$PROCESS_NAME process still running after uninstall!" >&2 + exit 1 +fi +echo "$PROCESS_NAME process successfully killed after uninstall" diff --git a/internal/otel_collector/internal/buildscripts/packaging/msi/make.ps1 b/internal/otel_collector/internal/buildscripts/packaging/msi/make.ps1 new file mode 100644 index 00000000000..6eff43433f9 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/msi/make.ps1 @@ -0,0 +1,75 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +<# +.SYNOPSIS + Makefile like build commands for the Collector on Windows. + + Usage: .\make.ps1 [- ...] + Example: .\make.ps1 New-MSI -Config "./my-config.yaml" -Version "v0.0.2" +.PARAMETER Target + Build target to run (Install-Tools, New-MSI) +#> +Param( + [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)][string]$Target +) + +$ErrorActionPreference = "Stop" + +function Install-Tools { + # disable progress bar support as this causes CircleCI to crash + $OriginalPref = $ProgressPreference + $ProgressPreference = "SilentlyContinue" + Install-WindowsFeature Net-Framework-Core + $ProgressPreference = $OriginalPref + + choco install wixtoolset -y + setx /m PATH "%PATH%;C:\Program Files (x86)\WiX Toolset v3.11\bin" + refreshenv +} + +function New-MSI( + [string]$Version="0.0.1", + [string]$Config="./examples/local/otel-config.yaml" +) { + candle -arch x64 -dVersion="$Version" -dConfig="$Config" internal/buildscripts/packaging/msi/opentelemetry-collector.wxs + light opentelemetry-collector.wixobj + mkdir dist -ErrorAction Ignore + Move-Item -Force opentelemetry-collector.msi dist/otel-collector-$Version-amd64.msi +} + +function Confirm-MSI { + # ensure system32 is in Path so we can use executables like msiexec & sc + $env:Path += ";C:\Windows\System32" + $msipath = Resolve-Path "$pwd\dist\otel-collector-*-amd64.msi" + + # install msi, validate service is installed & running + Start-Process -Wait msiexec "/i `"$msipath`" /qn" + sc.exe query state=all | findstr "otelcol" | Out-Null + if ($LASTEXITCODE -ne 0) { Throw "otelcol service failed to install" } + + # stop service + Stop-Service otelcol + + # start service + Start-Service otelcol + + # uninstall msi, validate service is uninstalled + Start-Process -Wait msiexec "/x `"$msipath`" /qn" + sc.exe query state=all | findstr "otelcol" | Out-Null + if ($LASTEXITCODE -ne 1) { Throw "otelcol service failed to uninstall" } +} + +$sb = [scriptblock]::create("$Target") +Invoke-Command -ScriptBlock $sb diff --git a/internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry-collector.wxs b/internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry-collector.wxs new file mode 100644 index 00000000000..c957b5b3fc2 --- /dev/null +++ b/internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry-collector.wxs @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONFIG AND NOT Installed + + + diff --git a/internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry.ico b/internal/otel_collector/internal/buildscripts/packaging/msi/opentelemetry.ico new file mode 100644 index 0000000000000000000000000000000000000000..9bdd4cf54e47f7fc7689dd285742eeff87ef9fd3 GIT binary patch literal 275598 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 YaaqN$u0JDM!SsZ+{N<-zBj2S8QZl>h($ literal 0 HcmV?d00001 diff --git a/internal/otel_collector/internal/collector/telemetry/telemetry.go b/internal/otel_collector/internal/collector/telemetry/telemetry.go new file mode 100644 index 00000000000..d56dee568fd --- /dev/null +++ b/internal/otel_collector/internal/collector/telemetry/telemetry.go @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package telemetry controls the telemetry settings to be used in the collector. +package telemetry + +import ( + "flag" + + "go.opentelemetry.io/collector/internal/version" +) + +const ( + metricsAddrCfg = "metrics-addr" + metricsPrefixCfg = "metrics-prefix" +) + +var ( + // Command-line flags that control publication of telemetry data. + metricsAddrPtr *string + metricsPrefixPtr *string + + addInstanceIDPtr *bool +) + +func Flags(flags *flag.FlagSet) { + // At least until we can use a generic, i.e.: OpenCensus, metrics exporter + // we default to Prometheus at port 8888, if not otherwise specified. + metricsAddrPtr = flags.String( + metricsAddrCfg, + GetMetricsAddrDefault(), + "[address]:port for exposing collector telemetry.") + + metricsPrefixPtr = flags.String( + metricsPrefixCfg, + "otelcol", + "Prefix to the metrics generated by the collector.") + + addInstanceIDPtr = flags.Bool( + "add-instance-id", + true, + "Flag to control the addition of 'service.instance.id' to the collector metrics.") +} + +// GetMetricsAddrDefault returns the default metrics bind address and port depending on +// the current build type. +func GetMetricsAddrDefault() string { + if version.IsDevBuild() { + // Listen on localhost by default for dev builds to avoid security prompts. + return "localhost:8888" + } + return ":8888" +} + +func GetAddInstanceID() bool { + return *addInstanceIDPtr +} + +func GetMetricsAddr() string { + return *metricsAddrPtr +} + +func GetMetricsPrefix() string { + return *metricsPrefixPtr +} diff --git a/internal/otel_collector/internal/data/.gitignore b/internal/otel_collector/internal/data/.gitignore new file mode 100644 index 00000000000..980a4a35c71 --- /dev/null +++ b/internal/otel_collector/internal/data/.gitignore @@ -0,0 +1 @@ +.patched-otlp-proto \ No newline at end of file diff --git a/internal/otel_collector/internal/data/bytesid.go b/internal/otel_collector/internal/data/bytesid.go new file mode 100644 index 00000000000..5b02eaa6a45 --- /dev/null +++ b/internal/otel_collector/internal/data/bytesid.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package data + +import ( + "encoding/hex" + "errors" + "fmt" +) + +// marshalJSON converts trace id into a hex string enclosed in quotes. +// Called by Protobuf JSON deserialization. +func marshalJSON(id []byte) ([]byte, error) { + if len(id) == 0 { + return []byte(`""`), nil + } + + // 2 chars per byte plus 2 quote chars at the start and end. + hexLen := 2*len(id) + 2 + + b := make([]byte, hexLen) + hex.Encode(b[1:hexLen-1], id) + b[0], b[hexLen-1] = '"', '"' + + return b, nil +} + +// unmarshalJSON inflates trace id from hex string, possibly enclosed in quotes. +// Called by Protobuf JSON deserialization. +func unmarshalJSON(dst []byte, src []byte) error { + if l := len(src); l >= 2 && src[0] == '"' && src[l-1] == '"' { + src = src[1 : l-1] + } + nLen := len(src) + if nLen == 0 { + return nil + } + + if len(dst) != hex.DecodedLen(nLen) { + return errors.New("invalid length for ID") + } + + _, err := hex.Decode(dst, src) + if err != nil { + return fmt.Errorf("cannot unmarshal ID from string '%s': %w", string(src), err) + } + return nil +} + +func marshalBytes(dst []byte, src []byte) (n int, err error) { + if len(dst) < len(src) { + return 0, errors.New("buffer is too short") + } + return copy(dst, src), nil +} diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.go new file mode 100644 index 00000000000..722bec013b6 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.go @@ -0,0 +1,558 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/collector/logs/v1/logs_service.proto + +package v1 + +import ( + context "context" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type ExportLogsServiceRequest struct { + // An array of ResourceLogs. + // For data coming from a single resource this array will typically contain one + // element. Intermediary nodes (such as OpenTelemetry Collector) that receive + // data from multiple origins typically batch the data before forwarding further and + // in that case this array will contain multiple elements. + ResourceLogs []*v1.ResourceLogs `protobuf:"bytes,1,rep,name=resource_logs,json=resourceLogs,proto3" json:"resource_logs,omitempty"` +} + +func (m *ExportLogsServiceRequest) Reset() { *m = ExportLogsServiceRequest{} } +func (m *ExportLogsServiceRequest) String() string { return proto.CompactTextString(m) } +func (*ExportLogsServiceRequest) ProtoMessage() {} +func (*ExportLogsServiceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8e3bf87aaa43acd4, []int{0} +} +func (m *ExportLogsServiceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExportLogsServiceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExportLogsServiceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExportLogsServiceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExportLogsServiceRequest.Merge(m, src) +} +func (m *ExportLogsServiceRequest) XXX_Size() int { + return m.Size() +} +func (m *ExportLogsServiceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ExportLogsServiceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ExportLogsServiceRequest proto.InternalMessageInfo + +func (m *ExportLogsServiceRequest) GetResourceLogs() []*v1.ResourceLogs { + if m != nil { + return m.ResourceLogs + } + return nil +} + +type ExportLogsServiceResponse struct { +} + +func (m *ExportLogsServiceResponse) Reset() { *m = ExportLogsServiceResponse{} } +func (m *ExportLogsServiceResponse) String() string { return proto.CompactTextString(m) } +func (*ExportLogsServiceResponse) ProtoMessage() {} +func (*ExportLogsServiceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8e3bf87aaa43acd4, []int{1} +} +func (m *ExportLogsServiceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExportLogsServiceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExportLogsServiceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExportLogsServiceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExportLogsServiceResponse.Merge(m, src) +} +func (m *ExportLogsServiceResponse) XXX_Size() int { + return m.Size() +} +func (m *ExportLogsServiceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ExportLogsServiceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ExportLogsServiceResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*ExportLogsServiceRequest)(nil), "opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest") + proto.RegisterType((*ExportLogsServiceResponse)(nil), "opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/collector/logs/v1/logs_service.proto", fileDescriptor_8e3bf87aaa43acd4) +} + +var fileDescriptor_8e3bf87aaa43acd4 = []byte{ + // 303 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xb2, 0xc8, 0x2f, 0x48, 0xcd, + 0x2b, 0x49, 0xcd, 0x49, 0xcd, 0x4d, 0x2d, 0x29, 0xaa, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, + 0x4f, 0xce, 0xcf, 0xc9, 0x49, 0x4d, 0x2e, 0xc9, 0x2f, 0xd2, 0xcf, 0xc9, 0x4f, 0x2f, 0xd6, 0x2f, + 0x33, 0x04, 0xd3, 0xf1, 0xc5, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x7a, 0x60, 0x45, 0x42, 0xaa, + 0x28, 0x3a, 0x21, 0x82, 0x7a, 0x70, 0x9d, 0x7a, 0x20, 0x1d, 0x7a, 0x65, 0x86, 0x52, 0x22, 0xe9, + 0xf9, 0xe9, 0xf9, 0x10, 0x63, 0x41, 0x2c, 0x88, 0x3a, 0x29, 0x35, 0x6c, 0xd6, 0x22, 0x5b, 0x06, + 0x51, 0xa7, 0x94, 0xc5, 0x25, 0xe1, 0x5a, 0x51, 0x90, 0x5f, 0x54, 0xe2, 0x93, 0x9f, 0x5e, 0x1c, + 0x0c, 0xb1, 0x3f, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0xc8, 0x8f, 0x8b, 0xb7, 0x28, 0xb5, + 0x38, 0xbf, 0xb4, 0x28, 0x39, 0x35, 0x1e, 0xa4, 0x45, 0x82, 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, + 0x53, 0x0f, 0x9b, 0xc3, 0xa0, 0xce, 0xd1, 0x0b, 0x82, 0xea, 0x00, 0x99, 0x17, 0xc4, 0x53, 0x84, + 0xc4, 0x53, 0x92, 0xe6, 0x92, 0xc4, 0x62, 0x57, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0xd1, 0x5c, + 0x46, 0x2e, 0x6e, 0x24, 0x71, 0xa1, 0x5e, 0x46, 0x2e, 0x36, 0x88, 0x6a, 0x21, 0x7b, 0x3d, 0xa2, + 0x42, 0x42, 0x0f, 0x97, 0x47, 0xa4, 0x1c, 0xc8, 0x37, 0x00, 0xe2, 0x3a, 0x25, 0x06, 0xa7, 0xb5, + 0x8c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, + 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0xc0, 0xa5, 0x91, 0x99, 0x4f, 0x9c, + 0x05, 0x4e, 0x02, 0x48, 0x66, 0x07, 0x80, 0xd4, 0x04, 0x30, 0x46, 0x85, 0xa6, 0xa3, 0xeb, 0xce, + 0x44, 0x4e, 0x20, 0x99, 0x79, 0x25, 0xa9, 0x45, 0x79, 0x89, 0x39, 0xfa, 0x29, 0x89, 0x25, 0x89, + 0xfa, 0x28, 0x0a, 0x75, 0xc1, 0xd6, 0xe8, 0xa6, 0xa7, 0xe6, 0x61, 0x26, 0xa8, 0x24, 0x36, 0xb0, + 0xa4, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x73, 0x09, 0x94, 0xaf, 0x80, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// LogsServiceClient is the client API for LogsService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type LogsServiceClient interface { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + Export(ctx context.Context, in *ExportLogsServiceRequest, opts ...grpc.CallOption) (*ExportLogsServiceResponse, error) +} + +type logsServiceClient struct { + cc *grpc.ClientConn +} + +func NewLogsServiceClient(cc *grpc.ClientConn) LogsServiceClient { + return &logsServiceClient{cc} +} + +func (c *logsServiceClient) Export(ctx context.Context, in *ExportLogsServiceRequest, opts ...grpc.CallOption) (*ExportLogsServiceResponse, error) { + out := new(ExportLogsServiceResponse) + err := c.cc.Invoke(ctx, "/opentelemetry.proto.collector.logs.v1.LogsService/Export", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LogsServiceServer is the server API for LogsService service. +type LogsServiceServer interface { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + Export(context.Context, *ExportLogsServiceRequest) (*ExportLogsServiceResponse, error) +} + +// UnimplementedLogsServiceServer can be embedded to have forward compatible implementations. +type UnimplementedLogsServiceServer struct { +} + +func (*UnimplementedLogsServiceServer) Export(ctx context.Context, req *ExportLogsServiceRequest) (*ExportLogsServiceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Export not implemented") +} + +func RegisterLogsServiceServer(s *grpc.Server, srv LogsServiceServer) { + s.RegisterService(&_LogsService_serviceDesc, srv) +} + +func _LogsService_Export_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportLogsServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LogsServiceServer).Export(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/opentelemetry.proto.collector.logs.v1.LogsService/Export", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LogsServiceServer).Export(ctx, req.(*ExportLogsServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _LogsService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "opentelemetry.proto.collector.logs.v1.LogsService", + HandlerType: (*LogsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Export", + Handler: _LogsService_Export_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "opentelemetry/proto/collector/logs/v1/logs_service.proto", +} + +func (m *ExportLogsServiceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExportLogsServiceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExportLogsServiceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ResourceLogs) > 0 { + for iNdEx := len(m.ResourceLogs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ResourceLogs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogsService(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ExportLogsServiceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExportLogsServiceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExportLogsServiceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintLogsService(dAtA []byte, offset int, v uint64) int { + offset -= sovLogsService(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ExportLogsServiceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ResourceLogs) > 0 { + for _, e := range m.ResourceLogs { + l = e.Size() + n += 1 + l + sovLogsService(uint64(l)) + } + } + return n +} + +func (m *ExportLogsServiceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovLogsService(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozLogsService(x uint64) (n int) { + return sovLogsService(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExportLogsServiceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogsService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExportLogsServiceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExportLogsServiceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceLogs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogsService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogsService + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogsService + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceLogs = append(m.ResourceLogs, &v1.ResourceLogs{}) + if err := m.ResourceLogs[len(m.ResourceLogs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLogsService(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLogsService + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthLogsService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExportLogsServiceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogsService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExportLogsServiceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExportLogsServiceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipLogsService(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLogsService + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthLogsService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLogsService(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLogsService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLogsService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLogsService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthLogsService + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupLogsService + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthLogsService + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthLogsService = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLogsService = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupLogsService = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.gw.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.gw.go new file mode 100644 index 00000000000..8003733add0 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/logs/v1/logs_service.pb.gw.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: opentelemetry/proto/collector/logs/v1/logs_service.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +func request_LogsService_Export_0(ctx context.Context, marshaler runtime.Marshaler, client LogsServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportLogsServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Export(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LogsService_Export_0(ctx context.Context, marshaler runtime.Marshaler, server LogsServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportLogsServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Export(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterLogsServiceHandlerServer registers the http handlers for service LogsService to "mux". +// UnaryRPC :call LogsServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterLogsServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server LogsServiceServer) error { + + mux.Handle("POST", pattern_LogsService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LogsService_Export_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_LogsService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterLogsServiceHandlerFromEndpoint is same as RegisterLogsServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterLogsServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterLogsServiceHandler(ctx, mux, conn) +} + +// RegisterLogsServiceHandler registers the http handlers for service LogsService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterLogsServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterLogsServiceHandlerClient(ctx, mux, NewLogsServiceClient(conn)) +} + +// RegisterLogsServiceHandlerClient registers the http handlers for service LogsService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "LogsServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LogsServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "LogsServiceClient" to call the correct interceptors. +func RegisterLogsServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LogsServiceClient) error { + + mux.Handle("POST", pattern_LogsService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LogsService_Export_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_LogsService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_LogsService_Export_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "logs"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_LogsService_Export_0 = runtime.ForwardResponseMessage +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.go new file mode 100644 index 00000000000..b0a984f1139 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.go @@ -0,0 +1,558 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/collector/metrics/v1/metrics_service.proto + +package v1 + +import ( + context "context" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type ExportMetricsServiceRequest struct { + // An array of ResourceMetrics. + // For data coming from a single resource this array will typically contain one + // element. Intermediary nodes (such as OpenTelemetry Collector) that receive + // data from multiple origins typically batch the data before forwarding further and + // in that case this array will contain multiple elements. + ResourceMetrics []*v1.ResourceMetrics `protobuf:"bytes,1,rep,name=resource_metrics,json=resourceMetrics,proto3" json:"resource_metrics,omitempty"` +} + +func (m *ExportMetricsServiceRequest) Reset() { *m = ExportMetricsServiceRequest{} } +func (m *ExportMetricsServiceRequest) String() string { return proto.CompactTextString(m) } +func (*ExportMetricsServiceRequest) ProtoMessage() {} +func (*ExportMetricsServiceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_75fb6015e6e64798, []int{0} +} +func (m *ExportMetricsServiceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExportMetricsServiceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExportMetricsServiceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExportMetricsServiceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExportMetricsServiceRequest.Merge(m, src) +} +func (m *ExportMetricsServiceRequest) XXX_Size() int { + return m.Size() +} +func (m *ExportMetricsServiceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ExportMetricsServiceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ExportMetricsServiceRequest proto.InternalMessageInfo + +func (m *ExportMetricsServiceRequest) GetResourceMetrics() []*v1.ResourceMetrics { + if m != nil { + return m.ResourceMetrics + } + return nil +} + +type ExportMetricsServiceResponse struct { +} + +func (m *ExportMetricsServiceResponse) Reset() { *m = ExportMetricsServiceResponse{} } +func (m *ExportMetricsServiceResponse) String() string { return proto.CompactTextString(m) } +func (*ExportMetricsServiceResponse) ProtoMessage() {} +func (*ExportMetricsServiceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_75fb6015e6e64798, []int{1} +} +func (m *ExportMetricsServiceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExportMetricsServiceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExportMetricsServiceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExportMetricsServiceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExportMetricsServiceResponse.Merge(m, src) +} +func (m *ExportMetricsServiceResponse) XXX_Size() int { + return m.Size() +} +func (m *ExportMetricsServiceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ExportMetricsServiceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ExportMetricsServiceResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*ExportMetricsServiceRequest)(nil), "opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest") + proto.RegisterType((*ExportMetricsServiceResponse)(nil), "opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/collector/metrics/v1/metrics_service.proto", fileDescriptor_75fb6015e6e64798) +} + +var fileDescriptor_75fb6015e6e64798 = []byte{ + // 304 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xb2, 0xcb, 0x2f, 0x48, 0xcd, + 0x2b, 0x49, 0xcd, 0x49, 0xcd, 0x4d, 0x2d, 0x29, 0xaa, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, + 0x4f, 0xce, 0xcf, 0xc9, 0x49, 0x4d, 0x2e, 0xc9, 0x2f, 0xd2, 0x07, 0x89, 0x66, 0x26, 0x17, 0xeb, + 0x97, 0x19, 0xc2, 0x98, 0xf1, 0xc5, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x7a, 0x60, 0xa5, 0x42, + 0x1a, 0x28, 0xfa, 0x21, 0x82, 0x7a, 0x70, 0xfd, 0x7a, 0x50, 0x4d, 0x7a, 0x65, 0x86, 0x52, 0x22, + 0xe9, 0xf9, 0xe9, 0xf9, 0x10, 0xf3, 0x41, 0x2c, 0x88, 0x52, 0x29, 0x1d, 0x6c, 0xf6, 0x63, 0xda, + 0x0a, 0x51, 0xad, 0x54, 0xc9, 0x25, 0xed, 0x5a, 0x51, 0x90, 0x5f, 0x54, 0xe2, 0x0b, 0x11, 0x0e, + 0x86, 0xb8, 0x25, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0x28, 0x8a, 0x4b, 0xa0, 0x28, 0xb5, + 0x38, 0xbf, 0xb4, 0x28, 0x39, 0x35, 0x1e, 0xaa, 0x51, 0x82, 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, + 0x5f, 0x0f, 0x9b, 0x3b, 0x11, 0xae, 0xd3, 0x0b, 0x82, 0xea, 0x83, 0x1a, 0x1c, 0xc4, 0x5f, 0x84, + 0x2a, 0xa0, 0x24, 0xc7, 0x25, 0x83, 0xdd, 0xea, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0xa3, 0x35, + 0x8c, 0x5c, 0x7c, 0xa8, 0x52, 0x42, 0x33, 0x19, 0xb9, 0xd8, 0x20, 0x7a, 0x84, 0x5c, 0xf5, 0x88, + 0x0d, 0x27, 0x3d, 0x3c, 0x1e, 0x94, 0x72, 0xa3, 0xd4, 0x18, 0x88, 0x63, 0x95, 0x18, 0x9c, 0xb6, + 0x31, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, + 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x03, 0x97, 0x76, 0x66, 0x3e, 0xd1, + 0xd6, 0x38, 0x09, 0xa3, 0xda, 0x10, 0x00, 0x52, 0x19, 0xc0, 0x18, 0x15, 0x91, 0x8e, 0x6e, 0x46, + 0x26, 0x72, 0xb2, 0xca, 0xcc, 0x2b, 0x49, 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x4f, 0x49, 0x2c, 0x49, + 0xd4, 0x47, 0x51, 0xa8, 0x0b, 0xb6, 0x4c, 0x37, 0x3d, 0x35, 0x0f, 0x6b, 0x32, 0x4c, 0x62, 0x03, + 0xcb, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x08, 0x8a, 0xe8, 0x11, 0xb9, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MetricsServiceClient is the client API for MetricsService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MetricsServiceClient interface { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + Export(ctx context.Context, in *ExportMetricsServiceRequest, opts ...grpc.CallOption) (*ExportMetricsServiceResponse, error) +} + +type metricsServiceClient struct { + cc *grpc.ClientConn +} + +func NewMetricsServiceClient(cc *grpc.ClientConn) MetricsServiceClient { + return &metricsServiceClient{cc} +} + +func (c *metricsServiceClient) Export(ctx context.Context, in *ExportMetricsServiceRequest, opts ...grpc.CallOption) (*ExportMetricsServiceResponse, error) { + out := new(ExportMetricsServiceResponse) + err := c.cc.Invoke(ctx, "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MetricsServiceServer is the server API for MetricsService service. +type MetricsServiceServer interface { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + Export(context.Context, *ExportMetricsServiceRequest) (*ExportMetricsServiceResponse, error) +} + +// UnimplementedMetricsServiceServer can be embedded to have forward compatible implementations. +type UnimplementedMetricsServiceServer struct { +} + +func (*UnimplementedMetricsServiceServer) Export(ctx context.Context, req *ExportMetricsServiceRequest) (*ExportMetricsServiceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Export not implemented") +} + +func RegisterMetricsServiceServer(s *grpc.Server, srv MetricsServiceServer) { + s.RegisterService(&_MetricsService_serviceDesc, srv) +} + +func _MetricsService_Export_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportMetricsServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MetricsServiceServer).Export(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MetricsServiceServer).Export(ctx, req.(*ExportMetricsServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _MetricsService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "opentelemetry.proto.collector.metrics.v1.MetricsService", + HandlerType: (*MetricsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Export", + Handler: _MetricsService_Export_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "opentelemetry/proto/collector/metrics/v1/metrics_service.proto", +} + +func (m *ExportMetricsServiceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExportMetricsServiceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExportMetricsServiceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ResourceMetrics) > 0 { + for iNdEx := len(m.ResourceMetrics) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ResourceMetrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetricsService(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ExportMetricsServiceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExportMetricsServiceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExportMetricsServiceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintMetricsService(dAtA []byte, offset int, v uint64) int { + offset -= sovMetricsService(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ExportMetricsServiceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ResourceMetrics) > 0 { + for _, e := range m.ResourceMetrics { + l = e.Size() + n += 1 + l + sovMetricsService(uint64(l)) + } + } + return n +} + +func (m *ExportMetricsServiceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovMetricsService(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozMetricsService(x uint64) (n int) { + return sovMetricsService(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExportMetricsServiceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetricsService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExportMetricsServiceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExportMetricsServiceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceMetrics", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetricsService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetricsService + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetricsService + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceMetrics = append(m.ResourceMetrics, &v1.ResourceMetrics{}) + if err := m.ResourceMetrics[len(m.ResourceMetrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetricsService(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetricsService + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetricsService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExportMetricsServiceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetricsService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExportMetricsServiceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExportMetricsServiceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipMetricsService(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetricsService + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetricsService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipMetricsService(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetricsService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetricsService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetricsService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthMetricsService + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupMetricsService + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthMetricsService + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthMetricsService = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowMetricsService = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupMetricsService = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.gw.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.gw.go new file mode 100644 index 00000000000..8158c98a624 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1/metrics_service.pb.gw.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: opentelemetry/proto/collector/metrics/v1/metrics_service.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +func request_MetricsService_Export_0(ctx context.Context, marshaler runtime.Marshaler, client MetricsServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportMetricsServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Export(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_MetricsService_Export_0(ctx context.Context, marshaler runtime.Marshaler, server MetricsServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportMetricsServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Export(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterMetricsServiceHandlerServer registers the http handlers for service MetricsService to "mux". +// UnaryRPC :call MetricsServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterMetricsServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server MetricsServiceServer) error { + + mux.Handle("POST", pattern_MetricsService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_MetricsService_Export_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_MetricsService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterMetricsServiceHandlerFromEndpoint is same as RegisterMetricsServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterMetricsServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterMetricsServiceHandler(ctx, mux, conn) +} + +// RegisterMetricsServiceHandler registers the http handlers for service MetricsService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterMetricsServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterMetricsServiceHandlerClient(ctx, mux, NewMetricsServiceClient(conn)) +} + +// RegisterMetricsServiceHandlerClient registers the http handlers for service MetricsService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "MetricsServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "MetricsServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "MetricsServiceClient" to call the correct interceptors. +func RegisterMetricsServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client MetricsServiceClient) error { + + mux.Handle("POST", pattern_MetricsService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_MetricsService_Export_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_MetricsService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_MetricsService_Export_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "metrics"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_MetricsService_Export_0 = runtime.ForwardResponseMessage +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_config.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_config.pb.go new file mode 100644 index 00000000000..deebad2314c --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_config.pb.go @@ -0,0 +1,1262 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/trace/v1/trace_config.proto + +package v1 + +import ( + encoding_binary "encoding/binary" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// How spans should be sampled: +// - Always off +// - Always on +// - Always follow the parent Span's decision (off if no parent). +type ConstantSampler_ConstantDecision int32 + +const ( + ConstantSampler_ALWAYS_OFF ConstantSampler_ConstantDecision = 0 + ConstantSampler_ALWAYS_ON ConstantSampler_ConstantDecision = 1 + ConstantSampler_ALWAYS_PARENT ConstantSampler_ConstantDecision = 2 +) + +var ConstantSampler_ConstantDecision_name = map[int32]string{ + 0: "ALWAYS_OFF", + 1: "ALWAYS_ON", + 2: "ALWAYS_PARENT", +} + +var ConstantSampler_ConstantDecision_value = map[string]int32{ + "ALWAYS_OFF": 0, + "ALWAYS_ON": 1, + "ALWAYS_PARENT": 2, +} + +func (x ConstantSampler_ConstantDecision) String() string { + return proto.EnumName(ConstantSampler_ConstantDecision_name, int32(x)) +} + +func (ConstantSampler_ConstantDecision) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_5936aa8fa6443e6f, []int{1, 0} +} + +// Global configuration of the trace service. All fields must be specified, or +// the default (zero) values will be used for each type. +type TraceConfig struct { + // The global default sampler used to make decisions on span sampling. + // + // Types that are valid to be assigned to Sampler: + // *TraceConfig_ConstantSampler + // *TraceConfig_TraceIdRatioBased + // *TraceConfig_RateLimitingSampler + Sampler isTraceConfig_Sampler `protobuf_oneof:"sampler"` + // The global default max number of attributes per span. + MaxNumberOfAttributes int64 `protobuf:"varint,4,opt,name=max_number_of_attributes,json=maxNumberOfAttributes,proto3" json:"max_number_of_attributes,omitempty"` + // The global default max number of annotation events per span. + MaxNumberOfTimedEvents int64 `protobuf:"varint,5,opt,name=max_number_of_timed_events,json=maxNumberOfTimedEvents,proto3" json:"max_number_of_timed_events,omitempty"` + // The global default max number of attributes per timed event. + MaxNumberOfAttributesPerTimedEvent int64 `protobuf:"varint,6,opt,name=max_number_of_attributes_per_timed_event,json=maxNumberOfAttributesPerTimedEvent,proto3" json:"max_number_of_attributes_per_timed_event,omitempty"` + // The global default max number of link entries per span. + MaxNumberOfLinks int64 `protobuf:"varint,7,opt,name=max_number_of_links,json=maxNumberOfLinks,proto3" json:"max_number_of_links,omitempty"` + // The global default max number of attributes per span. + MaxNumberOfAttributesPerLink int64 `protobuf:"varint,8,opt,name=max_number_of_attributes_per_link,json=maxNumberOfAttributesPerLink,proto3" json:"max_number_of_attributes_per_link,omitempty"` +} + +func (m *TraceConfig) Reset() { *m = TraceConfig{} } +func (m *TraceConfig) String() string { return proto.CompactTextString(m) } +func (*TraceConfig) ProtoMessage() {} +func (*TraceConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_5936aa8fa6443e6f, []int{0} +} +func (m *TraceConfig) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceConfig.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceConfig.Merge(m, src) +} +func (m *TraceConfig) XXX_Size() int { + return m.Size() +} +func (m *TraceConfig) XXX_DiscardUnknown() { + xxx_messageInfo_TraceConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceConfig proto.InternalMessageInfo + +type isTraceConfig_Sampler interface { + isTraceConfig_Sampler() + MarshalTo([]byte) (int, error) + Size() int +} + +type TraceConfig_ConstantSampler struct { + ConstantSampler *ConstantSampler `protobuf:"bytes,1,opt,name=constant_sampler,json=constantSampler,proto3,oneof" json:"constant_sampler,omitempty"` +} +type TraceConfig_TraceIdRatioBased struct { + TraceIdRatioBased *TraceIdRatioBased `protobuf:"bytes,2,opt,name=trace_id_ratio_based,json=traceIdRatioBased,proto3,oneof" json:"trace_id_ratio_based,omitempty"` +} +type TraceConfig_RateLimitingSampler struct { + RateLimitingSampler *RateLimitingSampler `protobuf:"bytes,3,opt,name=rate_limiting_sampler,json=rateLimitingSampler,proto3,oneof" json:"rate_limiting_sampler,omitempty"` +} + +func (*TraceConfig_ConstantSampler) isTraceConfig_Sampler() {} +func (*TraceConfig_TraceIdRatioBased) isTraceConfig_Sampler() {} +func (*TraceConfig_RateLimitingSampler) isTraceConfig_Sampler() {} + +func (m *TraceConfig) GetSampler() isTraceConfig_Sampler { + if m != nil { + return m.Sampler + } + return nil +} + +func (m *TraceConfig) GetConstantSampler() *ConstantSampler { + if x, ok := m.GetSampler().(*TraceConfig_ConstantSampler); ok { + return x.ConstantSampler + } + return nil +} + +func (m *TraceConfig) GetTraceIdRatioBased() *TraceIdRatioBased { + if x, ok := m.GetSampler().(*TraceConfig_TraceIdRatioBased); ok { + return x.TraceIdRatioBased + } + return nil +} + +func (m *TraceConfig) GetRateLimitingSampler() *RateLimitingSampler { + if x, ok := m.GetSampler().(*TraceConfig_RateLimitingSampler); ok { + return x.RateLimitingSampler + } + return nil +} + +func (m *TraceConfig) GetMaxNumberOfAttributes() int64 { + if m != nil { + return m.MaxNumberOfAttributes + } + return 0 +} + +func (m *TraceConfig) GetMaxNumberOfTimedEvents() int64 { + if m != nil { + return m.MaxNumberOfTimedEvents + } + return 0 +} + +func (m *TraceConfig) GetMaxNumberOfAttributesPerTimedEvent() int64 { + if m != nil { + return m.MaxNumberOfAttributesPerTimedEvent + } + return 0 +} + +func (m *TraceConfig) GetMaxNumberOfLinks() int64 { + if m != nil { + return m.MaxNumberOfLinks + } + return 0 +} + +func (m *TraceConfig) GetMaxNumberOfAttributesPerLink() int64 { + if m != nil { + return m.MaxNumberOfAttributesPerLink + } + return 0 +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*TraceConfig) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*TraceConfig_ConstantSampler)(nil), + (*TraceConfig_TraceIdRatioBased)(nil), + (*TraceConfig_RateLimitingSampler)(nil), + } +} + +// Sampler that always makes a constant decision on span sampling. +type ConstantSampler struct { + Decision ConstantSampler_ConstantDecision `protobuf:"varint,1,opt,name=decision,proto3,enum=opentelemetry.proto.trace.v1.ConstantSampler_ConstantDecision" json:"decision,omitempty"` +} + +func (m *ConstantSampler) Reset() { *m = ConstantSampler{} } +func (m *ConstantSampler) String() string { return proto.CompactTextString(m) } +func (*ConstantSampler) ProtoMessage() {} +func (*ConstantSampler) Descriptor() ([]byte, []int) { + return fileDescriptor_5936aa8fa6443e6f, []int{1} +} +func (m *ConstantSampler) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConstantSampler) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConstantSampler.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConstantSampler) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConstantSampler.Merge(m, src) +} +func (m *ConstantSampler) XXX_Size() int { + return m.Size() +} +func (m *ConstantSampler) XXX_DiscardUnknown() { + xxx_messageInfo_ConstantSampler.DiscardUnknown(m) +} + +var xxx_messageInfo_ConstantSampler proto.InternalMessageInfo + +func (m *ConstantSampler) GetDecision() ConstantSampler_ConstantDecision { + if m != nil { + return m.Decision + } + return ConstantSampler_ALWAYS_OFF +} + +// Sampler that tries to uniformly sample traces with a given ratio. +// The ratio of sampling a trace is equal to that of the specified ratio. +type TraceIdRatioBased struct { + // The desired ratio of sampling. Must be within [0.0, 1.0]. + SamplingRatio float64 `protobuf:"fixed64,1,opt,name=samplingRatio,proto3" json:"samplingRatio,omitempty"` +} + +func (m *TraceIdRatioBased) Reset() { *m = TraceIdRatioBased{} } +func (m *TraceIdRatioBased) String() string { return proto.CompactTextString(m) } +func (*TraceIdRatioBased) ProtoMessage() {} +func (*TraceIdRatioBased) Descriptor() ([]byte, []int) { + return fileDescriptor_5936aa8fa6443e6f, []int{2} +} +func (m *TraceIdRatioBased) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceIdRatioBased) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceIdRatioBased.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceIdRatioBased) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceIdRatioBased.Merge(m, src) +} +func (m *TraceIdRatioBased) XXX_Size() int { + return m.Size() +} +func (m *TraceIdRatioBased) XXX_DiscardUnknown() { + xxx_messageInfo_TraceIdRatioBased.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceIdRatioBased proto.InternalMessageInfo + +func (m *TraceIdRatioBased) GetSamplingRatio() float64 { + if m != nil { + return m.SamplingRatio + } + return 0 +} + +// Sampler that tries to sample with a rate per time window. +type RateLimitingSampler struct { + // Rate per second. + Qps int64 `protobuf:"varint,1,opt,name=qps,proto3" json:"qps,omitempty"` +} + +func (m *RateLimitingSampler) Reset() { *m = RateLimitingSampler{} } +func (m *RateLimitingSampler) String() string { return proto.CompactTextString(m) } +func (*RateLimitingSampler) ProtoMessage() {} +func (*RateLimitingSampler) Descriptor() ([]byte, []int) { + return fileDescriptor_5936aa8fa6443e6f, []int{3} +} +func (m *RateLimitingSampler) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RateLimitingSampler) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RateLimitingSampler.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RateLimitingSampler) XXX_Merge(src proto.Message) { + xxx_messageInfo_RateLimitingSampler.Merge(m, src) +} +func (m *RateLimitingSampler) XXX_Size() int { + return m.Size() +} +func (m *RateLimitingSampler) XXX_DiscardUnknown() { + xxx_messageInfo_RateLimitingSampler.DiscardUnknown(m) +} + +var xxx_messageInfo_RateLimitingSampler proto.InternalMessageInfo + +func (m *RateLimitingSampler) GetQps() int64 { + if m != nil { + return m.Qps + } + return 0 +} + +func init() { + proto.RegisterEnum("opentelemetry.proto.trace.v1.ConstantSampler_ConstantDecision", ConstantSampler_ConstantDecision_name, ConstantSampler_ConstantDecision_value) + proto.RegisterType((*TraceConfig)(nil), "opentelemetry.proto.trace.v1.TraceConfig") + proto.RegisterType((*ConstantSampler)(nil), "opentelemetry.proto.trace.v1.ConstantSampler") + proto.RegisterType((*TraceIdRatioBased)(nil), "opentelemetry.proto.trace.v1.TraceIdRatioBased") + proto.RegisterType((*RateLimitingSampler)(nil), "opentelemetry.proto.trace.v1.RateLimitingSampler") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/trace/v1/trace_config.proto", fileDescriptor_5936aa8fa6443e6f) +} + +var fileDescriptor_5936aa8fa6443e6f = []byte{ + // 565 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xcd, 0x6e, 0xd3, 0x40, + 0x14, 0x85, 0xed, 0x86, 0xfe, 0xdd, 0x2a, 0xad, 0x33, 0x69, 0x91, 0x55, 0x55, 0xa6, 0x58, 0x48, + 0x64, 0x93, 0x58, 0x29, 0x0b, 0x04, 0x0b, 0xa4, 0xa4, 0x3f, 0x14, 0x29, 0x4a, 0x23, 0x37, 0x02, + 0x91, 0x8d, 0x35, 0xb1, 0x27, 0xd6, 0x08, 0x7b, 0x26, 0x8c, 0xa7, 0x51, 0xd9, 0xf3, 0x00, 0xbc, + 0x04, 0x4f, 0xc0, 0x4b, 0xb0, 0xec, 0x92, 0x25, 0x4a, 0x5e, 0x04, 0x79, 0x9c, 0xa6, 0x71, 0xda, + 0x46, 0x62, 0x37, 0x73, 0xce, 0x9c, 0xef, 0xde, 0x49, 0xae, 0x07, 0x1c, 0x3e, 0x24, 0x4c, 0x92, + 0x88, 0xc4, 0x44, 0x8a, 0x6f, 0xce, 0x50, 0x70, 0xc9, 0x1d, 0x29, 0xb0, 0x4f, 0x9c, 0x51, 0x3d, + 0x5b, 0x78, 0x3e, 0x67, 0x03, 0x1a, 0xd6, 0x94, 0x87, 0x0e, 0x72, 0x81, 0x4c, 0xac, 0xa9, 0x73, + 0xb5, 0x51, 0x7d, 0x7f, 0x37, 0xe4, 0x21, 0xcf, 0x20, 0xe9, 0x2a, 0xb3, 0xed, 0xef, 0xab, 0xb0, + 0xd5, 0x4d, 0x8f, 0x1c, 0x2b, 0x12, 0xea, 0x81, 0xe1, 0x73, 0x96, 0x48, 0xcc, 0xa4, 0x97, 0xe0, + 0x78, 0x18, 0x11, 0x61, 0xea, 0x87, 0x7a, 0x65, 0xeb, 0xa8, 0x5a, 0x5b, 0x86, 0xaf, 0x1d, 0x4f, + 0x53, 0x97, 0x59, 0xe8, 0x5c, 0x73, 0x77, 0xfc, 0xbc, 0x84, 0xfa, 0xb0, 0x9b, 0x75, 0x4d, 0x03, + 0x4f, 0x60, 0x49, 0xb9, 0xd7, 0xc7, 0x09, 0x09, 0xcc, 0x15, 0xc5, 0x77, 0x96, 0xf3, 0x55, 0x93, + 0x1f, 0x02, 0x37, 0xcd, 0x35, 0xd3, 0xd8, 0xb9, 0xe6, 0x96, 0xe4, 0xa2, 0x88, 0x42, 0xd8, 0x13, + 0x58, 0x12, 0x2f, 0xa2, 0x31, 0x95, 0x94, 0x85, 0xb3, 0x4b, 0x14, 0x54, 0x91, 0xfa, 0xf2, 0x22, + 0x2e, 0x96, 0xa4, 0x35, 0x4d, 0xde, 0x5d, 0xa4, 0x2c, 0xee, 0xcb, 0xe8, 0x35, 0x98, 0x31, 0xbe, + 0xf6, 0xd8, 0x55, 0xdc, 0x27, 0xc2, 0xe3, 0x03, 0x0f, 0x4b, 0x29, 0x68, 0xff, 0x4a, 0x92, 0xc4, + 0x7c, 0x72, 0xa8, 0x57, 0x0a, 0xee, 0x5e, 0x8c, 0xaf, 0xdb, 0xca, 0xbe, 0x18, 0x34, 0x66, 0x26, + 0x7a, 0x0b, 0xfb, 0xf9, 0xa0, 0xa4, 0x31, 0x09, 0x3c, 0x32, 0x22, 0x4c, 0x26, 0xe6, 0xaa, 0x8a, + 0x3e, 0x9d, 0x8b, 0x76, 0x53, 0xfb, 0x54, 0xb9, 0xa8, 0x0b, 0x95, 0xc7, 0x8a, 0x7a, 0x43, 0x22, + 0xe6, 0x51, 0xe6, 0x9a, 0x22, 0xd9, 0x0f, 0x36, 0xd1, 0x21, 0xe2, 0x0e, 0x8b, 0xaa, 0x50, 0xce, + 0x53, 0x23, 0xca, 0xbe, 0x24, 0xe6, 0xba, 0x02, 0x18, 0x73, 0x80, 0x56, 0xaa, 0xa3, 0xf7, 0xf0, + 0x7c, 0x69, 0x13, 0x69, 0xda, 0xdc, 0x50, 0xe1, 0x83, 0xc7, 0xaa, 0xa7, 0xa4, 0xe6, 0x26, 0xac, + 0x4f, 0xff, 0x1d, 0xfb, 0x97, 0x0e, 0x3b, 0x0b, 0x13, 0x84, 0x7a, 0xb0, 0x11, 0x10, 0x9f, 0x26, + 0x94, 0x33, 0x35, 0x82, 0xdb, 0x47, 0xef, 0xfe, 0x6b, 0x04, 0x67, 0xfb, 0x93, 0x29, 0xc5, 0x9d, + 0xf1, 0xec, 0x13, 0x30, 0x16, 0x5d, 0xb4, 0x0d, 0xd0, 0x68, 0x7d, 0x6a, 0x7c, 0xbe, 0xf4, 0x2e, + 0xce, 0xce, 0x0c, 0x0d, 0x15, 0x61, 0xf3, 0x76, 0xdf, 0x36, 0x74, 0x54, 0x82, 0xe2, 0x74, 0xdb, + 0x69, 0xb8, 0xa7, 0xed, 0xae, 0xb1, 0x62, 0xbf, 0x81, 0xd2, 0xbd, 0xb1, 0x44, 0x2f, 0xa0, 0xa8, + 0x6e, 0x45, 0x59, 0xa8, 0x54, 0xd5, 0xbb, 0xee, 0xe6, 0x45, 0xfb, 0x25, 0x94, 0x1f, 0x18, 0x36, + 0x64, 0x40, 0xe1, 0xeb, 0x30, 0x51, 0x91, 0x82, 0x9b, 0x2e, 0x9b, 0x3f, 0xf5, 0xdf, 0x63, 0x4b, + 0xbf, 0x19, 0x5b, 0xfa, 0xdf, 0xb1, 0xa5, 0xff, 0x98, 0x58, 0xda, 0xcd, 0xc4, 0xd2, 0xfe, 0x4c, + 0x2c, 0x0d, 0x9e, 0x51, 0xbe, 0xf4, 0x07, 0x69, 0x1a, 0x73, 0x5f, 0x76, 0x27, 0xb5, 0x3a, 0x7a, + 0xef, 0x63, 0xb8, 0x18, 0xa2, 0xdc, 0xf1, 0x79, 0x14, 0x11, 0x5f, 0x72, 0xe1, 0x50, 0x26, 0x89, + 0x60, 0x38, 0x72, 0x02, 0x2c, 0x71, 0xfe, 0x05, 0xaa, 0x2a, 0x7a, 0x35, 0x24, 0x6c, 0xee, 0xfc, + 0xed, 0x7b, 0xd4, 0x5f, 0x53, 0xee, 0xab, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x4f, 0xc7, 0xe9, + 0xec, 0xb6, 0x04, 0x00, 0x00, +} + +func (m *TraceConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceConfig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MaxNumberOfAttributesPerLink != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.MaxNumberOfAttributesPerLink)) + i-- + dAtA[i] = 0x40 + } + if m.MaxNumberOfLinks != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.MaxNumberOfLinks)) + i-- + dAtA[i] = 0x38 + } + if m.MaxNumberOfAttributesPerTimedEvent != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.MaxNumberOfAttributesPerTimedEvent)) + i-- + dAtA[i] = 0x30 + } + if m.MaxNumberOfTimedEvents != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.MaxNumberOfTimedEvents)) + i-- + dAtA[i] = 0x28 + } + if m.MaxNumberOfAttributes != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.MaxNumberOfAttributes)) + i-- + dAtA[i] = 0x20 + } + if m.Sampler != nil { + { + size := m.Sampler.Size() + i -= size + if _, err := m.Sampler.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *TraceConfig_ConstantSampler) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceConfig_ConstantSampler) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ConstantSampler != nil { + { + size, err := m.ConstantSampler.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTraceConfig(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} +func (m *TraceConfig_TraceIdRatioBased) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceConfig_TraceIdRatioBased) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.TraceIdRatioBased != nil { + { + size, err := m.TraceIdRatioBased.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTraceConfig(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} +func (m *TraceConfig_RateLimitingSampler) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceConfig_RateLimitingSampler) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.RateLimitingSampler != nil { + { + size, err := m.RateLimitingSampler.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTraceConfig(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + return len(dAtA) - i, nil +} +func (m *ConstantSampler) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConstantSampler) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConstantSampler) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Decision != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.Decision)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *TraceIdRatioBased) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceIdRatioBased) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceIdRatioBased) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.SamplingRatio != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.SamplingRatio)))) + i-- + dAtA[i] = 0x9 + } + return len(dAtA) - i, nil +} + +func (m *RateLimitingSampler) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RateLimitingSampler) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RateLimitingSampler) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Qps != 0 { + i = encodeVarintTraceConfig(dAtA, i, uint64(m.Qps)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTraceConfig(dAtA []byte, offset int, v uint64) int { + offset -= sovTraceConfig(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *TraceConfig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sampler != nil { + n += m.Sampler.Size() + } + if m.MaxNumberOfAttributes != 0 { + n += 1 + sovTraceConfig(uint64(m.MaxNumberOfAttributes)) + } + if m.MaxNumberOfTimedEvents != 0 { + n += 1 + sovTraceConfig(uint64(m.MaxNumberOfTimedEvents)) + } + if m.MaxNumberOfAttributesPerTimedEvent != 0 { + n += 1 + sovTraceConfig(uint64(m.MaxNumberOfAttributesPerTimedEvent)) + } + if m.MaxNumberOfLinks != 0 { + n += 1 + sovTraceConfig(uint64(m.MaxNumberOfLinks)) + } + if m.MaxNumberOfAttributesPerLink != 0 { + n += 1 + sovTraceConfig(uint64(m.MaxNumberOfAttributesPerLink)) + } + return n +} + +func (m *TraceConfig_ConstantSampler) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ConstantSampler != nil { + l = m.ConstantSampler.Size() + n += 1 + l + sovTraceConfig(uint64(l)) + } + return n +} +func (m *TraceConfig_TraceIdRatioBased) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TraceIdRatioBased != nil { + l = m.TraceIdRatioBased.Size() + n += 1 + l + sovTraceConfig(uint64(l)) + } + return n +} +func (m *TraceConfig_RateLimitingSampler) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.RateLimitingSampler != nil { + l = m.RateLimitingSampler.Size() + n += 1 + l + sovTraceConfig(uint64(l)) + } + return n +} +func (m *ConstantSampler) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Decision != 0 { + n += 1 + sovTraceConfig(uint64(m.Decision)) + } + return n +} + +func (m *TraceIdRatioBased) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SamplingRatio != 0 { + n += 9 + } + return n +} + +func (m *RateLimitingSampler) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Qps != 0 { + n += 1 + sovTraceConfig(uint64(m.Qps)) + } + return n +} + +func sovTraceConfig(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTraceConfig(x uint64) (n int) { + return sovTraceConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *TraceConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TraceConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TraceConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConstantSampler", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTraceConfig + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTraceConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &ConstantSampler{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Sampler = &TraceConfig_ConstantSampler{v} + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceIdRatioBased", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTraceConfig + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTraceConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &TraceIdRatioBased{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Sampler = &TraceConfig_TraceIdRatioBased{v} + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RateLimitingSampler", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTraceConfig + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTraceConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &RateLimitingSampler{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Sampler = &TraceConfig_RateLimitingSampler{v} + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxNumberOfAttributes", wireType) + } + m.MaxNumberOfAttributes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxNumberOfAttributes |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxNumberOfTimedEvents", wireType) + } + m.MaxNumberOfTimedEvents = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxNumberOfTimedEvents |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxNumberOfAttributesPerTimedEvent", wireType) + } + m.MaxNumberOfAttributesPerTimedEvent = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxNumberOfAttributesPerTimedEvent |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxNumberOfLinks", wireType) + } + m.MaxNumberOfLinks = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxNumberOfLinks |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxNumberOfAttributesPerLink", wireType) + } + m.MaxNumberOfAttributesPerLink = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxNumberOfAttributesPerLink |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTraceConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConstantSampler) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConstantSampler: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConstantSampler: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Decision", wireType) + } + m.Decision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Decision |= ConstantSampler_ConstantDecision(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTraceConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceIdRatioBased) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TraceIdRatioBased: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TraceIdRatioBased: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field SamplingRatio", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.SamplingRatio = float64(math.Float64frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipTraceConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RateLimitingSampler) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RateLimitingSampler: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RateLimitingSampler: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Qps", wireType) + } + m.Qps = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Qps |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTraceConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTraceConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTraceConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTraceConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTraceConfig + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTraceConfig + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTraceConfig + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTraceConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTraceConfig = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTraceConfig = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.go new file mode 100644 index 00000000000..a96e40ea8a3 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.go @@ -0,0 +1,559 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/collector/trace/v1/trace_service.proto + +package v1 + +import ( + context "context" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type ExportTraceServiceRequest struct { + // An array of ResourceSpans. + // For data coming from a single resource this array will typically contain one + // element. Intermediary nodes (such as OpenTelemetry Collector) that receive + // data from multiple origins typically batch the data before forwarding further and + // in that case this array will contain multiple elements. + ResourceSpans []*v1.ResourceSpans `protobuf:"bytes,1,rep,name=resource_spans,json=resourceSpans,proto3" json:"resource_spans,omitempty"` +} + +func (m *ExportTraceServiceRequest) Reset() { *m = ExportTraceServiceRequest{} } +func (m *ExportTraceServiceRequest) String() string { return proto.CompactTextString(m) } +func (*ExportTraceServiceRequest) ProtoMessage() {} +func (*ExportTraceServiceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_192a962890318cf4, []int{0} +} +func (m *ExportTraceServiceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExportTraceServiceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExportTraceServiceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExportTraceServiceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExportTraceServiceRequest.Merge(m, src) +} +func (m *ExportTraceServiceRequest) XXX_Size() int { + return m.Size() +} +func (m *ExportTraceServiceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ExportTraceServiceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ExportTraceServiceRequest proto.InternalMessageInfo + +func (m *ExportTraceServiceRequest) GetResourceSpans() []*v1.ResourceSpans { + if m != nil { + return m.ResourceSpans + } + return nil +} + +type ExportTraceServiceResponse struct { +} + +func (m *ExportTraceServiceResponse) Reset() { *m = ExportTraceServiceResponse{} } +func (m *ExportTraceServiceResponse) String() string { return proto.CompactTextString(m) } +func (*ExportTraceServiceResponse) ProtoMessage() {} +func (*ExportTraceServiceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_192a962890318cf4, []int{1} +} +func (m *ExportTraceServiceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExportTraceServiceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExportTraceServiceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExportTraceServiceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExportTraceServiceResponse.Merge(m, src) +} +func (m *ExportTraceServiceResponse) XXX_Size() int { + return m.Size() +} +func (m *ExportTraceServiceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ExportTraceServiceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ExportTraceServiceResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*ExportTraceServiceRequest)(nil), "opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest") + proto.RegisterType((*ExportTraceServiceResponse)(nil), "opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/collector/trace/v1/trace_service.proto", fileDescriptor_192a962890318cf4) +} + +var fileDescriptor_192a962890318cf4 = []byte{ + // 306 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xb2, 0xca, 0x2f, 0x48, 0xcd, + 0x2b, 0x49, 0xcd, 0x49, 0xcd, 0x4d, 0x2d, 0x29, 0xaa, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, + 0x4f, 0xce, 0xcf, 0xc9, 0x49, 0x4d, 0x2e, 0xc9, 0x2f, 0xd2, 0x2f, 0x29, 0x4a, 0x4c, 0x4e, 0xd5, + 0x2f, 0x33, 0x84, 0x30, 0xe2, 0x8b, 0x53, 0x8b, 0xca, 0x32, 0x93, 0x53, 0xf5, 0xc0, 0xca, 0x84, + 0xd4, 0x50, 0xf4, 0x42, 0x04, 0xf5, 0xe0, 0x7a, 0xf5, 0xc0, 0x5a, 0xf4, 0xca, 0x0c, 0xa5, 0x44, + 0xd2, 0xf3, 0xd3, 0xf3, 0x21, 0x26, 0x83, 0x58, 0x10, 0x85, 0x52, 0x1a, 0xd8, 0x6c, 0x46, 0xb5, + 0x0f, 0xa2, 0x52, 0x29, 0x9f, 0x4b, 0xd2, 0xb5, 0xa2, 0x20, 0xbf, 0xa8, 0x24, 0x04, 0x24, 0x18, + 0x0c, 0x71, 0x43, 0x50, 0x6a, 0x61, 0x69, 0x6a, 0x71, 0x89, 0x50, 0x10, 0x17, 0x5f, 0x51, 0x6a, + 0x71, 0x7e, 0x69, 0x11, 0xc8, 0x79, 0x05, 0x89, 0x79, 0xc5, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0xdc, + 0x46, 0xda, 0x7a, 0xd8, 0x5c, 0x07, 0x73, 0x93, 0x5e, 0x10, 0x54, 0x4f, 0x30, 0x48, 0x4b, 0x10, + 0x6f, 0x11, 0x32, 0x57, 0x49, 0x86, 0x4b, 0x0a, 0x9b, 0x85, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, + 0x46, 0x8b, 0x18, 0xb9, 0x78, 0x90, 0x25, 0x84, 0x26, 0x32, 0x72, 0xb1, 0x41, 0xd4, 0x0b, 0x39, + 0xea, 0x11, 0x17, 0x26, 0x7a, 0x38, 0x3d, 0x24, 0xe5, 0x44, 0x89, 0x11, 0x10, 0x27, 0x2a, 0x31, + 0x38, 0x6d, 0x60, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, + 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x06, 0x2e, 0xcd, 0xcc, + 0x7c, 0x22, 0xad, 0x70, 0x12, 0x44, 0x36, 0x3d, 0x00, 0xa4, 0x2a, 0x80, 0x31, 0x2a, 0x2c, 0x1d, + 0x5d, 0x7f, 0x26, 0x72, 0x92, 0xc9, 0xcc, 0x2b, 0x49, 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x4f, 0x49, + 0x2c, 0x49, 0xd4, 0x47, 0x51, 0xa8, 0x0b, 0xb6, 0x48, 0x37, 0x3d, 0x35, 0x0f, 0x4b, 0x12, 0x4b, + 0x62, 0x03, 0xcb, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x4c, 0x62, 0xbb, 0x93, 0x02, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// TraceServiceClient is the client API for TraceService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type TraceServiceClient interface { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + Export(ctx context.Context, in *ExportTraceServiceRequest, opts ...grpc.CallOption) (*ExportTraceServiceResponse, error) +} + +type traceServiceClient struct { + cc *grpc.ClientConn +} + +func NewTraceServiceClient(cc *grpc.ClientConn) TraceServiceClient { + return &traceServiceClient{cc} +} + +func (c *traceServiceClient) Export(ctx context.Context, in *ExportTraceServiceRequest, opts ...grpc.CallOption) (*ExportTraceServiceResponse, error) { + out := new(ExportTraceServiceResponse) + err := c.cc.Invoke(ctx, "/opentelemetry.proto.collector.trace.v1.TraceService/Export", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TraceServiceServer is the server API for TraceService service. +type TraceServiceServer interface { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + Export(context.Context, *ExportTraceServiceRequest) (*ExportTraceServiceResponse, error) +} + +// UnimplementedTraceServiceServer can be embedded to have forward compatible implementations. +type UnimplementedTraceServiceServer struct { +} + +func (*UnimplementedTraceServiceServer) Export(ctx context.Context, req *ExportTraceServiceRequest) (*ExportTraceServiceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Export not implemented") +} + +func RegisterTraceServiceServer(s *grpc.Server, srv TraceServiceServer) { + s.RegisterService(&_TraceService_serviceDesc, srv) +} + +func _TraceService_Export_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportTraceServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TraceServiceServer).Export(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/opentelemetry.proto.collector.trace.v1.TraceService/Export", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TraceServiceServer).Export(ctx, req.(*ExportTraceServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _TraceService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "opentelemetry.proto.collector.trace.v1.TraceService", + HandlerType: (*TraceServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Export", + Handler: _TraceService_Export_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "opentelemetry/proto/collector/trace/v1/trace_service.proto", +} + +func (m *ExportTraceServiceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExportTraceServiceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExportTraceServiceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ResourceSpans) > 0 { + for iNdEx := len(m.ResourceSpans) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ResourceSpans[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTraceService(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ExportTraceServiceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExportTraceServiceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExportTraceServiceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTraceService(dAtA []byte, offset int, v uint64) int { + offset -= sovTraceService(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ExportTraceServiceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ResourceSpans) > 0 { + for _, e := range m.ResourceSpans { + l = e.Size() + n += 1 + l + sovTraceService(uint64(l)) + } + } + return n +} + +func (m *ExportTraceServiceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTraceService(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTraceService(x uint64) (n int) { + return sovTraceService(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExportTraceServiceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExportTraceServiceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExportTraceServiceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceSpans", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTraceService + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTraceService + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceSpans = append(m.ResourceSpans, &v1.ResourceSpans{}) + if err := m.ResourceSpans[len(m.ResourceSpans)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTraceService(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTraceService + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTraceService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExportTraceServiceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTraceService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExportTraceServiceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExportTraceServiceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTraceService(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTraceService + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTraceService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTraceService(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTraceService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTraceService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTraceService + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTraceService + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTraceService + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTraceService + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTraceService = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTraceService = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTraceService = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.gw.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.gw.go new file mode 100644 index 00000000000..1da38f1cd28 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service.pb.gw.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: opentelemetry/proto/collector/trace/v1/trace_service.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +func request_TraceService_Export_0(ctx context.Context, marshaler runtime.Marshaler, client TraceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportTraceServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Export(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TraceService_Export_0(ctx context.Context, marshaler runtime.Marshaler, server TraceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportTraceServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Export(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterTraceServiceHandlerServer registers the http handlers for service TraceService to "mux". +// UnaryRPC :call TraceServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterTraceServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TraceServiceServer) error { + + mux.Handle("POST", pattern_TraceService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TraceService_Export_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TraceService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterTraceServiceHandlerFromEndpoint is same as RegisterTraceServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterTraceServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterTraceServiceHandler(ctx, mux, conn) +} + +// RegisterTraceServiceHandler registers the http handlers for service TraceService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterTraceServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterTraceServiceHandlerClient(ctx, mux, NewTraceServiceClient(conn)) +} + +// RegisterTraceServiceHandlerClient registers the http handlers for service TraceService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "TraceServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "TraceServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "TraceServiceClient" to call the correct interceptors. +func RegisterTraceServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TraceServiceClient) error { + + mux.Handle("POST", pattern_TraceService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TraceService_Export_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TraceService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_TraceService_Export_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "trace"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_TraceService_Export_0 = runtime.ForwardResponseMessage +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service_gateway_aliases.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service_gateway_aliases.go new file mode 100644 index 00000000000..21dfb731e9e --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/collector/trace/v1/trace_service_gateway_aliases.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +import ( + context "context" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" +) + +// The aliases in this file are necessary to fix the bug: +// https://github.com/open-telemetry/opentelemetry-collector/issues/1968 + +// patternTraceServiceExport0Alias is an alias for the incorrect pattern +// pattern_TraceService_Export_0 defined in trace_service.pb.gw.go. +// +// The path in the pattern_TraceService_Export_0 pattern is incorrect because it is +// composed from the historical name of the package v1.trace used in the Protobuf +// declarations in trace_service.proto file and results in the path of /v1/trace. +// +// This is incorrect since the OTLP spec requires the default path to be /v1/traces, +// see https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/protocol/otlp.md#request. +// +// We set the correct path in this alias. +var patternTraceServiceExport0Alias = runtime.MustPattern( + runtime.NewPattern( + 1, + []int{2, 0, 2, 1}, + []string{"v1", "traces"}, // Patch the path to be /v1/traces. + "", + runtime.AssumeColonVerbOpt(true)), +) + +// RegisterTraceServiceHandlerServerAlias registers the http handlers for service TraceService to "mux". +// UnaryRPC :call TraceServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// +// RegisterTraceServiceHandlerServerAlias is the alias version of +// RegisterTraceServiceHandlerServer, and uses patternTraceServiceExport0Alias +// instead of pattern_TraceService_Export_0. +func RegisterTraceServiceHandlerServerAlias(ctx context.Context, mux *runtime.ServeMux, server TraceServiceServer) error { + + // pattern_TraceService_Export_0 is replaced by patternTraceServiceExport0Alias + // in the following line. This is the only change in this func compared to + // RegisterTraceServiceHandlerServer. + mux.Handle("POST", patternTraceServiceExport0Alias, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TraceService_Export_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TraceService_Export_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/common/v1/common.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/common/v1/common.pb.go new file mode 100644 index 00000000000..3b362407449 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/common/v1/common.pb.go @@ -0,0 +1,1781 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/common/v1/common.proto + +package v1 + +import ( + encoding_binary "encoding/binary" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// AnyValue is used to represent any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +type AnyValue struct { + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "null". + // + // Types that are valid to be assigned to Value: + // *AnyValue_StringValue + // *AnyValue_BoolValue + // *AnyValue_IntValue + // *AnyValue_DoubleValue + // *AnyValue_ArrayValue + // *AnyValue_KvlistValue + Value isAnyValue_Value `protobuf_oneof:"value"` +} + +func (m *AnyValue) Reset() { *m = AnyValue{} } +func (m *AnyValue) String() string { return proto.CompactTextString(m) } +func (*AnyValue) ProtoMessage() {} +func (*AnyValue) Descriptor() ([]byte, []int) { + return fileDescriptor_62ba46dcb97aa817, []int{0} +} +func (m *AnyValue) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AnyValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AnyValue.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AnyValue) XXX_Merge(src proto.Message) { + xxx_messageInfo_AnyValue.Merge(m, src) +} +func (m *AnyValue) XXX_Size() int { + return m.Size() +} +func (m *AnyValue) XXX_DiscardUnknown() { + xxx_messageInfo_AnyValue.DiscardUnknown(m) +} + +var xxx_messageInfo_AnyValue proto.InternalMessageInfo + +type isAnyValue_Value interface { + isAnyValue_Value() + MarshalTo([]byte) (int, error) + Size() int +} + +type AnyValue_StringValue struct { + StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof" json:"string_value,omitempty"` +} +type AnyValue_BoolValue struct { + BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof" json:"bool_value,omitempty"` +} +type AnyValue_IntValue struct { + IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof" json:"int_value,omitempty"` +} +type AnyValue_DoubleValue struct { + DoubleValue float64 `protobuf:"fixed64,4,opt,name=double_value,json=doubleValue,proto3,oneof" json:"double_value,omitempty"` +} +type AnyValue_ArrayValue struct { + ArrayValue *ArrayValue `protobuf:"bytes,5,opt,name=array_value,json=arrayValue,proto3,oneof" json:"array_value,omitempty"` +} +type AnyValue_KvlistValue struct { + KvlistValue *KeyValueList `protobuf:"bytes,6,opt,name=kvlist_value,json=kvlistValue,proto3,oneof" json:"kvlist_value,omitempty"` +} + +func (*AnyValue_StringValue) isAnyValue_Value() {} +func (*AnyValue_BoolValue) isAnyValue_Value() {} +func (*AnyValue_IntValue) isAnyValue_Value() {} +func (*AnyValue_DoubleValue) isAnyValue_Value() {} +func (*AnyValue_ArrayValue) isAnyValue_Value() {} +func (*AnyValue_KvlistValue) isAnyValue_Value() {} + +func (m *AnyValue) GetValue() isAnyValue_Value { + if m != nil { + return m.Value + } + return nil +} + +func (m *AnyValue) GetStringValue() string { + if x, ok := m.GetValue().(*AnyValue_StringValue); ok { + return x.StringValue + } + return "" +} + +func (m *AnyValue) GetBoolValue() bool { + if x, ok := m.GetValue().(*AnyValue_BoolValue); ok { + return x.BoolValue + } + return false +} + +func (m *AnyValue) GetIntValue() int64 { + if x, ok := m.GetValue().(*AnyValue_IntValue); ok { + return x.IntValue + } + return 0 +} + +func (m *AnyValue) GetDoubleValue() float64 { + if x, ok := m.GetValue().(*AnyValue_DoubleValue); ok { + return x.DoubleValue + } + return 0 +} + +func (m *AnyValue) GetArrayValue() *ArrayValue { + if x, ok := m.GetValue().(*AnyValue_ArrayValue); ok { + return x.ArrayValue + } + return nil +} + +func (m *AnyValue) GetKvlistValue() *KeyValueList { + if x, ok := m.GetValue().(*AnyValue_KvlistValue); ok { + return x.KvlistValue + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*AnyValue) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*AnyValue_StringValue)(nil), + (*AnyValue_BoolValue)(nil), + (*AnyValue_IntValue)(nil), + (*AnyValue_DoubleValue)(nil), + (*AnyValue_ArrayValue)(nil), + (*AnyValue_KvlistValue)(nil), + } +} + +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +type ArrayValue struct { + // Array of values. The array may be empty (contain 0 elements). + Values []AnyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values"` +} + +func (m *ArrayValue) Reset() { *m = ArrayValue{} } +func (m *ArrayValue) String() string { return proto.CompactTextString(m) } +func (*ArrayValue) ProtoMessage() {} +func (*ArrayValue) Descriptor() ([]byte, []int) { + return fileDescriptor_62ba46dcb97aa817, []int{1} +} +func (m *ArrayValue) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ArrayValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ArrayValue.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ArrayValue) XXX_Merge(src proto.Message) { + xxx_messageInfo_ArrayValue.Merge(m, src) +} +func (m *ArrayValue) XXX_Size() int { + return m.Size() +} +func (m *ArrayValue) XXX_DiscardUnknown() { + xxx_messageInfo_ArrayValue.DiscardUnknown(m) +} + +var xxx_messageInfo_ArrayValue proto.InternalMessageInfo + +func (m *ArrayValue) GetValues() []AnyValue { + if m != nil { + return m.Values + } + return nil +} + +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +type KeyValueList struct { + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + Values []KeyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values"` +} + +func (m *KeyValueList) Reset() { *m = KeyValueList{} } +func (m *KeyValueList) String() string { return proto.CompactTextString(m) } +func (*KeyValueList) ProtoMessage() {} +func (*KeyValueList) Descriptor() ([]byte, []int) { + return fileDescriptor_62ba46dcb97aa817, []int{2} +} +func (m *KeyValueList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *KeyValueList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_KeyValueList.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *KeyValueList) XXX_Merge(src proto.Message) { + xxx_messageInfo_KeyValueList.Merge(m, src) +} +func (m *KeyValueList) XXX_Size() int { + return m.Size() +} +func (m *KeyValueList) XXX_DiscardUnknown() { + xxx_messageInfo_KeyValueList.DiscardUnknown(m) +} + +var xxx_messageInfo_KeyValueList proto.InternalMessageInfo + +func (m *KeyValueList) GetValues() []KeyValue { + if m != nil { + return m.Values + } + return nil +} + +// KeyValue is a key-value pair that is used to store Span attributes, Link +// attributes, etc. +type KeyValue struct { + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value AnyValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value"` +} + +func (m *KeyValue) Reset() { *m = KeyValue{} } +func (m *KeyValue) String() string { return proto.CompactTextString(m) } +func (*KeyValue) ProtoMessage() {} +func (*KeyValue) Descriptor() ([]byte, []int) { + return fileDescriptor_62ba46dcb97aa817, []int{3} +} +func (m *KeyValue) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *KeyValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_KeyValue.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *KeyValue) XXX_Merge(src proto.Message) { + xxx_messageInfo_KeyValue.Merge(m, src) +} +func (m *KeyValue) XXX_Size() int { + return m.Size() +} +func (m *KeyValue) XXX_DiscardUnknown() { + xxx_messageInfo_KeyValue.DiscardUnknown(m) +} + +var xxx_messageInfo_KeyValue proto.InternalMessageInfo + +func (m *KeyValue) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *KeyValue) GetValue() AnyValue { + if m != nil { + return m.Value + } + return AnyValue{} +} + +// StringKeyValue is a pair of key/value strings. This is the simpler (and faster) version +// of KeyValue that only supports string values. +type StringKeyValue struct { + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *StringKeyValue) Reset() { *m = StringKeyValue{} } +func (m *StringKeyValue) String() string { return proto.CompactTextString(m) } +func (*StringKeyValue) ProtoMessage() {} +func (*StringKeyValue) Descriptor() ([]byte, []int) { + return fileDescriptor_62ba46dcb97aa817, []int{4} +} +func (m *StringKeyValue) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StringKeyValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StringKeyValue.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *StringKeyValue) XXX_Merge(src proto.Message) { + xxx_messageInfo_StringKeyValue.Merge(m, src) +} +func (m *StringKeyValue) XXX_Size() int { + return m.Size() +} +func (m *StringKeyValue) XXX_DiscardUnknown() { + xxx_messageInfo_StringKeyValue.DiscardUnknown(m) +} + +var xxx_messageInfo_StringKeyValue proto.InternalMessageInfo + +func (m *StringKeyValue) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *StringKeyValue) GetValue() string { + if m != nil { + return m.Value + } + return "" +} + +// InstrumentationLibrary is a message representing the instrumentation library information +// such as the fully qualified name and version. +type InstrumentationLibrary struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` +} + +func (m *InstrumentationLibrary) Reset() { *m = InstrumentationLibrary{} } +func (m *InstrumentationLibrary) String() string { return proto.CompactTextString(m) } +func (*InstrumentationLibrary) ProtoMessage() {} +func (*InstrumentationLibrary) Descriptor() ([]byte, []int) { + return fileDescriptor_62ba46dcb97aa817, []int{5} +} +func (m *InstrumentationLibrary) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InstrumentationLibrary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InstrumentationLibrary.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InstrumentationLibrary) XXX_Merge(src proto.Message) { + xxx_messageInfo_InstrumentationLibrary.Merge(m, src) +} +func (m *InstrumentationLibrary) XXX_Size() int { + return m.Size() +} +func (m *InstrumentationLibrary) XXX_DiscardUnknown() { + xxx_messageInfo_InstrumentationLibrary.DiscardUnknown(m) +} + +var xxx_messageInfo_InstrumentationLibrary proto.InternalMessageInfo + +func (m *InstrumentationLibrary) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *InstrumentationLibrary) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func init() { + proto.RegisterType((*AnyValue)(nil), "opentelemetry.proto.common.v1.AnyValue") + proto.RegisterType((*ArrayValue)(nil), "opentelemetry.proto.common.v1.ArrayValue") + proto.RegisterType((*KeyValueList)(nil), "opentelemetry.proto.common.v1.KeyValueList") + proto.RegisterType((*KeyValue)(nil), "opentelemetry.proto.common.v1.KeyValue") + proto.RegisterType((*StringKeyValue)(nil), "opentelemetry.proto.common.v1.StringKeyValue") + proto.RegisterType((*InstrumentationLibrary)(nil), "opentelemetry.proto.common.v1.InstrumentationLibrary") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/common/v1/common.proto", fileDescriptor_62ba46dcb97aa817) +} + +var fileDescriptor_62ba46dcb97aa817 = []byte{ + // 467 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4d, 0x6b, 0xdb, 0x30, + 0x18, 0xb6, 0x9a, 0x34, 0x4d, 0x5e, 0x87, 0x31, 0x44, 0x19, 0x61, 0x50, 0xd7, 0x64, 0x87, 0x79, + 0x1b, 0x8d, 0x69, 0x77, 0xd9, 0xb5, 0x29, 0x1b, 0x19, 0xcb, 0x20, 0xb8, 0x6c, 0x87, 0x5d, 0x86, + 0xd2, 0x0a, 0x23, 0x2a, 0x4b, 0x45, 0x56, 0x0c, 0xfe, 0x17, 0x3b, 0xee, 0xcf, 0xec, 0xde, 0x63, + 0x8f, 0x3b, 0x8d, 0x91, 0xfc, 0x91, 0xa2, 0x0f, 0xf7, 0xeb, 0x90, 0x92, 0xdb, 0xab, 0x47, 0xcf, + 0xc7, 0xfb, 0xea, 0x03, 0xde, 0xca, 0x4b, 0x2a, 0x34, 0xe5, 0xb4, 0xa0, 0x5a, 0xd5, 0xe9, 0xa5, + 0x92, 0x5a, 0xa6, 0x67, 0xb2, 0x28, 0xa4, 0x48, 0xab, 0x43, 0x5f, 0x8d, 0x2c, 0x8c, 0xf7, 0x1e, + 0x70, 0x1d, 0x38, 0xf2, 0x8c, 0xea, 0xf0, 0xe5, 0x6e, 0x2e, 0x73, 0xe9, 0x0c, 0x4c, 0xe5, 0xf6, + 0x87, 0x7f, 0xb6, 0xa0, 0x7b, 0x2c, 0xea, 0xef, 0x84, 0x2f, 0x28, 0x7e, 0x05, 0xfd, 0x52, 0x2b, + 0x26, 0xf2, 0x9f, 0x95, 0x59, 0x0f, 0x50, 0x8c, 0x92, 0xde, 0x24, 0xc8, 0x42, 0x87, 0x3a, 0xd2, + 0x3e, 0xc0, 0x5c, 0x4a, 0xee, 0x29, 0x5b, 0x31, 0x4a, 0xba, 0x93, 0x20, 0xeb, 0x19, 0xcc, 0x11, + 0xf6, 0xa0, 0xc7, 0x84, 0xf6, 0xfb, 0xad, 0x18, 0x25, 0xad, 0x49, 0x90, 0x75, 0x99, 0xd0, 0xb7, + 0x21, 0xe7, 0x72, 0x31, 0xe7, 0xd4, 0x33, 0xda, 0x31, 0x4a, 0x90, 0x09, 0x71, 0xa8, 0x23, 0x4d, + 0x21, 0x24, 0x4a, 0x91, 0xda, 0x73, 0xb6, 0x63, 0x94, 0x84, 0x47, 0x6f, 0x46, 0x6b, 0x27, 0x1c, + 0x1d, 0x1b, 0x85, 0xd5, 0x4f, 0x82, 0x0c, 0xc8, 0xed, 0x0a, 0xcf, 0xa0, 0x7f, 0x51, 0x71, 0x56, + 0x36, 0x4d, 0x75, 0xac, 0xdd, 0xbb, 0x27, 0xec, 0xbe, 0x50, 0x27, 0x9f, 0xb2, 0x52, 0x9b, 0xfe, + 0x9c, 0x85, 0x85, 0xc6, 0x3b, 0xb0, 0x6d, 0xad, 0x86, 0xa7, 0x00, 0x77, 0xb1, 0xf8, 0x23, 0x74, + 0x2c, 0x5c, 0x0e, 0x50, 0xdc, 0x4a, 0xc2, 0xa3, 0xd7, 0x4f, 0x75, 0xec, 0x4f, 0x7e, 0xdc, 0xbe, + 0xfa, 0xb7, 0x1f, 0x64, 0x5e, 0x3c, 0xfc, 0x06, 0xfd, 0xfb, 0xe1, 0x1b, 0xdb, 0x36, 0xe2, 0x47, + 0xb6, 0x04, 0xba, 0xcd, 0x0e, 0x7e, 0x0e, 0xad, 0x0b, 0x5a, 0xbb, 0x1b, 0xce, 0x4c, 0x89, 0x4f, + 0xfc, 0x48, 0xf6, 0x4a, 0x37, 0x6e, 0xdd, 0x1f, 0xc7, 0x07, 0x78, 0x76, 0x6a, 0xdf, 0xca, 0x9a, + 0xa0, 0xdd, 0xfb, 0x41, 0xbd, 0x46, 0xf9, 0x09, 0x5e, 0x7c, 0x16, 0xa5, 0x56, 0x8b, 0x82, 0x0a, + 0x4d, 0x34, 0x93, 0x62, 0xca, 0xe6, 0x8a, 0xa8, 0x1a, 0x63, 0x68, 0x0b, 0x52, 0xf8, 0xd7, 0x98, + 0xd9, 0x1a, 0x0f, 0x60, 0xa7, 0xa2, 0xaa, 0x64, 0x52, 0x78, 0x97, 0x66, 0x39, 0xfe, 0x8d, 0xae, + 0x96, 0x11, 0xba, 0x5e, 0x46, 0xe8, 0xff, 0x32, 0x42, 0xbf, 0x56, 0x51, 0x70, 0xbd, 0x8a, 0x82, + 0xbf, 0xab, 0x28, 0x80, 0x98, 0xc9, 0xf5, 0x43, 0x8d, 0xc3, 0x13, 0x5b, 0xce, 0x0c, 0x3c, 0x43, + 0x3f, 0xbe, 0xe6, 0x8f, 0x05, 0xcc, 0xfc, 0x3e, 0xce, 0xe9, 0x99, 0x96, 0x2a, 0x65, 0x42, 0x53, + 0x25, 0x08, 0x4f, 0xcf, 0x89, 0x26, 0xe9, 0x03, 0xe2, 0x81, 0x75, 0x3e, 0xc8, 0xa9, 0xb8, 0xfb, + 0xad, 0xf3, 0x8e, 0x05, 0xdf, 0xdf, 0x04, 0x00, 0x00, 0xff, 0xff, 0x70, 0x0b, 0x4b, 0xbe, 0xd5, + 0x03, 0x00, 0x00, +} + +func (m *AnyValue) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AnyValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Value != nil { + { + size := m.Value.Size() + i -= size + if _, err := m.Value.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *AnyValue_StringValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue_StringValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i -= len(m.StringValue) + copy(dAtA[i:], m.StringValue) + i = encodeVarintCommon(dAtA, i, uint64(len(m.StringValue))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} +func (m *AnyValue_BoolValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue_BoolValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i-- + if m.BoolValue { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + return len(dAtA) - i, nil +} +func (m *AnyValue_IntValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintCommon(dAtA, i, uint64(m.IntValue)) + i-- + dAtA[i] = 0x18 + return len(dAtA) - i, nil +} +func (m *AnyValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue)))) + i-- + dAtA[i] = 0x21 + return len(dAtA) - i, nil +} +func (m *AnyValue_ArrayValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue_ArrayValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ArrayValue != nil { + { + size, err := m.ArrayValue.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCommon(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + return len(dAtA) - i, nil +} +func (m *AnyValue_KvlistValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnyValue_KvlistValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.KvlistValue != nil { + { + size, err := m.KvlistValue.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCommon(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + return len(dAtA) - i, nil +} +func (m *ArrayValue) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ArrayValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ArrayValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Values) > 0 { + for iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Values[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCommon(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *KeyValueList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *KeyValueList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *KeyValueList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Values) > 0 { + for iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Values[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCommon(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *KeyValue) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *KeyValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *KeyValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Value.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCommon(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintCommon(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *StringKeyValue) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StringKeyValue) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StringKeyValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Value) > 0 { + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintCommon(dAtA, i, uint64(len(m.Value))) + i-- + dAtA[i] = 0x12 + } + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintCommon(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *InstrumentationLibrary) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InstrumentationLibrary) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InstrumentationLibrary) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintCommon(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintCommon(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintCommon(dAtA []byte, offset int, v uint64) int { + offset -= sovCommon(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *AnyValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Value != nil { + n += m.Value.Size() + } + return n +} + +func (m *AnyValue_StringValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.StringValue) + n += 1 + l + sovCommon(uint64(l)) + return n +} +func (m *AnyValue_BoolValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 2 + return n +} +func (m *AnyValue_IntValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovCommon(uint64(m.IntValue)) + return n +} +func (m *AnyValue_DoubleValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 9 + return n +} +func (m *AnyValue_ArrayValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ArrayValue != nil { + l = m.ArrayValue.Size() + n += 1 + l + sovCommon(uint64(l)) + } + return n +} +func (m *AnyValue_KvlistValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.KvlistValue != nil { + l = m.KvlistValue.Size() + n += 1 + l + sovCommon(uint64(l)) + } + return n +} +func (m *ArrayValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Values) > 0 { + for _, e := range m.Values { + l = e.Size() + n += 1 + l + sovCommon(uint64(l)) + } + } + return n +} + +func (m *KeyValueList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Values) > 0 { + for _, e := range m.Values { + l = e.Size() + n += 1 + l + sovCommon(uint64(l)) + } + } + return n +} + +func (m *KeyValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovCommon(uint64(l)) + } + l = m.Value.Size() + n += 1 + l + sovCommon(uint64(l)) + return n +} + +func (m *StringKeyValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovCommon(uint64(l)) + } + l = len(m.Value) + if l > 0 { + n += 1 + l + sovCommon(uint64(l)) + } + return n +} + +func (m *InstrumentationLibrary) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + if l > 0 { + n += 1 + l + sovCommon(uint64(l)) + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovCommon(uint64(l)) + } + return n +} + +func sovCommon(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozCommon(x uint64) (n int) { + return sovCommon(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *AnyValue) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AnyValue: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnyValue: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StringValue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = &AnyValue_StringValue{string(dAtA[iNdEx:postIndex])} + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BoolValue", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Value = &AnyValue_BoolValue{b} + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IntValue", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Value = &AnyValue_IntValue{v} + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field DoubleValue", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Value = &AnyValue_DoubleValue{float64(math.Float64frombits(v))} + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ArrayValue", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &ArrayValue{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Value = &AnyValue_ArrayValue{v} + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KvlistValue", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &KeyValueList{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Value = &AnyValue_KvlistValue{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommon(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ArrayValue) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ArrayValue: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ArrayValue: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Values = append(m.Values, AnyValue{}) + if err := m.Values[len(m.Values)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommon(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *KeyValueList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: KeyValueList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: KeyValueList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Values = append(m.Values, KeyValue{}) + if err := m.Values[len(m.Values)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommon(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *KeyValue) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: KeyValue: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: KeyValue: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Value.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommon(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StringKeyValue) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StringKeyValue: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StringKeyValue: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommon(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InstrumentationLibrary) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InstrumentationLibrary: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InstrumentationLibrary: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommon + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommon + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommon(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCommon + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCommon(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommon + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommon + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommon + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthCommon + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupCommon + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthCommon + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthCommon = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCommon = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupCommon = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/logs/v1/logs.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/logs/v1/logs.pb.go new file mode 100644 index 00000000000..3092a71e09b --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/logs/v1/logs.pb.go @@ -0,0 +1,1387 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/logs/v1/logs.proto + +package v1 + +import ( + encoding_binary "encoding/binary" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + + go_opentelemetry_io_collector_internal_data "go.opentelemetry.io/collector/internal/data" + v11 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Possible values for LogRecord.SeverityNumber. +type SeverityNumber int32 + +const ( + // UNSPECIFIED is the default SeverityNumber, it MUST not be used. + SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED SeverityNumber = 0 + SeverityNumber_SEVERITY_NUMBER_TRACE SeverityNumber = 1 + SeverityNumber_SEVERITY_NUMBER_TRACE2 SeverityNumber = 2 + SeverityNumber_SEVERITY_NUMBER_TRACE3 SeverityNumber = 3 + SeverityNumber_SEVERITY_NUMBER_TRACE4 SeverityNumber = 4 + SeverityNumber_SEVERITY_NUMBER_DEBUG SeverityNumber = 5 + SeverityNumber_SEVERITY_NUMBER_DEBUG2 SeverityNumber = 6 + SeverityNumber_SEVERITY_NUMBER_DEBUG3 SeverityNumber = 7 + SeverityNumber_SEVERITY_NUMBER_DEBUG4 SeverityNumber = 8 + SeverityNumber_SEVERITY_NUMBER_INFO SeverityNumber = 9 + SeverityNumber_SEVERITY_NUMBER_INFO2 SeverityNumber = 10 + SeverityNumber_SEVERITY_NUMBER_INFO3 SeverityNumber = 11 + SeverityNumber_SEVERITY_NUMBER_INFO4 SeverityNumber = 12 + SeverityNumber_SEVERITY_NUMBER_WARN SeverityNumber = 13 + SeverityNumber_SEVERITY_NUMBER_WARN2 SeverityNumber = 14 + SeverityNumber_SEVERITY_NUMBER_WARN3 SeverityNumber = 15 + SeverityNumber_SEVERITY_NUMBER_WARN4 SeverityNumber = 16 + SeverityNumber_SEVERITY_NUMBER_ERROR SeverityNumber = 17 + SeverityNumber_SEVERITY_NUMBER_ERROR2 SeverityNumber = 18 + SeverityNumber_SEVERITY_NUMBER_ERROR3 SeverityNumber = 19 + SeverityNumber_SEVERITY_NUMBER_ERROR4 SeverityNumber = 20 + SeverityNumber_SEVERITY_NUMBER_FATAL SeverityNumber = 21 + SeverityNumber_SEVERITY_NUMBER_FATAL2 SeverityNumber = 22 + SeverityNumber_SEVERITY_NUMBER_FATAL3 SeverityNumber = 23 + SeverityNumber_SEVERITY_NUMBER_FATAL4 SeverityNumber = 24 +) + +var SeverityNumber_name = map[int32]string{ + 0: "SEVERITY_NUMBER_UNSPECIFIED", + 1: "SEVERITY_NUMBER_TRACE", + 2: "SEVERITY_NUMBER_TRACE2", + 3: "SEVERITY_NUMBER_TRACE3", + 4: "SEVERITY_NUMBER_TRACE4", + 5: "SEVERITY_NUMBER_DEBUG", + 6: "SEVERITY_NUMBER_DEBUG2", + 7: "SEVERITY_NUMBER_DEBUG3", + 8: "SEVERITY_NUMBER_DEBUG4", + 9: "SEVERITY_NUMBER_INFO", + 10: "SEVERITY_NUMBER_INFO2", + 11: "SEVERITY_NUMBER_INFO3", + 12: "SEVERITY_NUMBER_INFO4", + 13: "SEVERITY_NUMBER_WARN", + 14: "SEVERITY_NUMBER_WARN2", + 15: "SEVERITY_NUMBER_WARN3", + 16: "SEVERITY_NUMBER_WARN4", + 17: "SEVERITY_NUMBER_ERROR", + 18: "SEVERITY_NUMBER_ERROR2", + 19: "SEVERITY_NUMBER_ERROR3", + 20: "SEVERITY_NUMBER_ERROR4", + 21: "SEVERITY_NUMBER_FATAL", + 22: "SEVERITY_NUMBER_FATAL2", + 23: "SEVERITY_NUMBER_FATAL3", + 24: "SEVERITY_NUMBER_FATAL4", +} + +var SeverityNumber_value = map[string]int32{ + "SEVERITY_NUMBER_UNSPECIFIED": 0, + "SEVERITY_NUMBER_TRACE": 1, + "SEVERITY_NUMBER_TRACE2": 2, + "SEVERITY_NUMBER_TRACE3": 3, + "SEVERITY_NUMBER_TRACE4": 4, + "SEVERITY_NUMBER_DEBUG": 5, + "SEVERITY_NUMBER_DEBUG2": 6, + "SEVERITY_NUMBER_DEBUG3": 7, + "SEVERITY_NUMBER_DEBUG4": 8, + "SEVERITY_NUMBER_INFO": 9, + "SEVERITY_NUMBER_INFO2": 10, + "SEVERITY_NUMBER_INFO3": 11, + "SEVERITY_NUMBER_INFO4": 12, + "SEVERITY_NUMBER_WARN": 13, + "SEVERITY_NUMBER_WARN2": 14, + "SEVERITY_NUMBER_WARN3": 15, + "SEVERITY_NUMBER_WARN4": 16, + "SEVERITY_NUMBER_ERROR": 17, + "SEVERITY_NUMBER_ERROR2": 18, + "SEVERITY_NUMBER_ERROR3": 19, + "SEVERITY_NUMBER_ERROR4": 20, + "SEVERITY_NUMBER_FATAL": 21, + "SEVERITY_NUMBER_FATAL2": 22, + "SEVERITY_NUMBER_FATAL3": 23, + "SEVERITY_NUMBER_FATAL4": 24, +} + +func (x SeverityNumber) String() string { + return proto.EnumName(SeverityNumber_name, int32(x)) +} + +func (SeverityNumber) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d1c030a3ec7e961e, []int{0} +} + +// Masks for LogRecord.flags field. +type LogRecordFlags int32 + +const ( + LogRecordFlags_LOG_RECORD_FLAG_UNSPECIFIED LogRecordFlags = 0 + LogRecordFlags_LOG_RECORD_FLAG_TRACE_FLAGS_MASK LogRecordFlags = 255 +) + +var LogRecordFlags_name = map[int32]string{ + 0: "LOG_RECORD_FLAG_UNSPECIFIED", + 255: "LOG_RECORD_FLAG_TRACE_FLAGS_MASK", +} + +var LogRecordFlags_value = map[string]int32{ + "LOG_RECORD_FLAG_UNSPECIFIED": 0, + "LOG_RECORD_FLAG_TRACE_FLAGS_MASK": 255, +} + +func (x LogRecordFlags) String() string { + return proto.EnumName(LogRecordFlags_name, int32(x)) +} + +func (LogRecordFlags) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d1c030a3ec7e961e, []int{1} +} + +// A collection of InstrumentationLibraryLogs from a Resource. +type ResourceLogs struct { + // The resource for the logs in this message. + // If this field is not set then no resource info is known. + Resource v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource"` + // A list of InstrumentationLibraryLogs that originate from a resource. + InstrumentationLibraryLogs []*InstrumentationLibraryLogs `protobuf:"bytes,2,rep,name=instrumentation_library_logs,json=instrumentationLibraryLogs,proto3" json:"instrumentation_library_logs,omitempty"` +} + +func (m *ResourceLogs) Reset() { *m = ResourceLogs{} } +func (m *ResourceLogs) String() string { return proto.CompactTextString(m) } +func (*ResourceLogs) ProtoMessage() {} +func (*ResourceLogs) Descriptor() ([]byte, []int) { + return fileDescriptor_d1c030a3ec7e961e, []int{0} +} +func (m *ResourceLogs) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResourceLogs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResourceLogs.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ResourceLogs) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResourceLogs.Merge(m, src) +} +func (m *ResourceLogs) XXX_Size() int { + return m.Size() +} +func (m *ResourceLogs) XXX_DiscardUnknown() { + xxx_messageInfo_ResourceLogs.DiscardUnknown(m) +} + +var xxx_messageInfo_ResourceLogs proto.InternalMessageInfo + +func (m *ResourceLogs) GetResource() v1.Resource { + if m != nil { + return m.Resource + } + return v1.Resource{} +} + +func (m *ResourceLogs) GetInstrumentationLibraryLogs() []*InstrumentationLibraryLogs { + if m != nil { + return m.InstrumentationLibraryLogs + } + return nil +} + +// A collection of Logs produced by an InstrumentationLibrary. +type InstrumentationLibraryLogs struct { + // The instrumentation library information for the logs in this message. + // If this field is not set then no library info is known. + InstrumentationLibrary v11.InstrumentationLibrary `protobuf:"bytes,1,opt,name=instrumentation_library,json=instrumentationLibrary,proto3" json:"instrumentation_library"` + // A list of log records. + Logs []*LogRecord `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` +} + +func (m *InstrumentationLibraryLogs) Reset() { *m = InstrumentationLibraryLogs{} } +func (m *InstrumentationLibraryLogs) String() string { return proto.CompactTextString(m) } +func (*InstrumentationLibraryLogs) ProtoMessage() {} +func (*InstrumentationLibraryLogs) Descriptor() ([]byte, []int) { + return fileDescriptor_d1c030a3ec7e961e, []int{1} +} +func (m *InstrumentationLibraryLogs) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InstrumentationLibraryLogs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InstrumentationLibraryLogs.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InstrumentationLibraryLogs) XXX_Merge(src proto.Message) { + xxx_messageInfo_InstrumentationLibraryLogs.Merge(m, src) +} +func (m *InstrumentationLibraryLogs) XXX_Size() int { + return m.Size() +} +func (m *InstrumentationLibraryLogs) XXX_DiscardUnknown() { + xxx_messageInfo_InstrumentationLibraryLogs.DiscardUnknown(m) +} + +var xxx_messageInfo_InstrumentationLibraryLogs proto.InternalMessageInfo + +func (m *InstrumentationLibraryLogs) GetInstrumentationLibrary() v11.InstrumentationLibrary { + if m != nil { + return m.InstrumentationLibrary + } + return v11.InstrumentationLibrary{} +} + +func (m *InstrumentationLibraryLogs) GetLogs() []*LogRecord { + if m != nil { + return m.Logs + } + return nil +} + +// A log record according to OpenTelemetry Log Data Model: +// https://github.com/open-telemetry/oteps/blob/master/text/logs/0097-log-data-model.md +type LogRecord struct { + // time_unix_nano is the time when the event occurred. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // Value of 0 indicates unknown or missing timestamp. + TimeUnixNano uint64 `protobuf:"fixed64,1,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // Numerical value of the severity, normalized to values described in Log Data Model. + // [Optional]. + SeverityNumber SeverityNumber `protobuf:"varint,2,opt,name=severity_number,json=severityNumber,proto3,enum=opentelemetry.proto.logs.v1.SeverityNumber" json:"severity_number,omitempty"` + // The severity text (also known as log level). The original string representation as + // it is known at the source. [Optional]. + SeverityText string `protobuf:"bytes,3,opt,name=severity_text,json=severityText,proto3" json:"severity_text,omitempty"` + // Short event identifier that does not contain varying parts. Name describes + // what happened (e.g. "ProcessStarted"). Recommended to be no longer than 50 + // characters. Not guaranteed to be unique in any way. [Optional]. + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + // A value containing the body of the log record. Can be for example a human-readable + // string message (including multi-line) describing the event in a free form or it can + // be a structured data composed of arrays and maps of other values. [Optional]. + Body v11.AnyValue `protobuf:"bytes,5,opt,name=body,proto3" json:"body"` + // Additional attributes that describe the specific event occurrence. [Optional]. + Attributes []v11.KeyValue `protobuf:"bytes,6,rep,name=attributes,proto3" json:"attributes"` + DroppedAttributesCount uint32 `protobuf:"varint,7,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // Flags, a bit field. 8 least significant bits are the trace flags as + // defined in W3C Trace Context specification. 24 most significant bits are reserved + // and must be set to 0. Readers must not assume that 24 most significant bits + // will be zero and must correctly mask the bits when reading 8-bit trace flag (use + // flags & TRACE_FLAGS_MASK). [Optional]. + Flags uint32 `protobuf:"fixed32,8,opt,name=flags,proto3" json:"flags,omitempty"` + // A unique identifier for a trace. All logs from the same trace share + // the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes + // is considered invalid. Can be set for logs that are part of request processing + // and have an assigned trace id. [Optional]. + TraceId go_opentelemetry_io_collector_internal_data.TraceID `protobuf:"bytes,9,opt,name=trace_id,json=traceId,proto3,customtype=go.opentelemetry.io/collector/internal/data.TraceID" json:"trace_id"` + // A unique identifier for a span within a trace, assigned when the span + // is created. The ID is an 8-byte array. An ID with all zeroes is considered + // invalid. Can be set for logs that are part of a particular processing span. + // If span_id is present trace_id SHOULD be also present. [Optional]. + SpanId go_opentelemetry_io_collector_internal_data.SpanID `protobuf:"bytes,10,opt,name=span_id,json=spanId,proto3,customtype=go.opentelemetry.io/collector/internal/data.SpanID" json:"span_id"` +} + +func (m *LogRecord) Reset() { *m = LogRecord{} } +func (m *LogRecord) String() string { return proto.CompactTextString(m) } +func (*LogRecord) ProtoMessage() {} +func (*LogRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_d1c030a3ec7e961e, []int{2} +} +func (m *LogRecord) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *LogRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_LogRecord.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *LogRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_LogRecord.Merge(m, src) +} +func (m *LogRecord) XXX_Size() int { + return m.Size() +} +func (m *LogRecord) XXX_DiscardUnknown() { + xxx_messageInfo_LogRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_LogRecord proto.InternalMessageInfo + +func (m *LogRecord) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *LogRecord) GetSeverityNumber() SeverityNumber { + if m != nil { + return m.SeverityNumber + } + return SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED +} + +func (m *LogRecord) GetSeverityText() string { + if m != nil { + return m.SeverityText + } + return "" +} + +func (m *LogRecord) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *LogRecord) GetBody() v11.AnyValue { + if m != nil { + return m.Body + } + return v11.AnyValue{} +} + +func (m *LogRecord) GetAttributes() []v11.KeyValue { + if m != nil { + return m.Attributes + } + return nil +} + +func (m *LogRecord) GetDroppedAttributesCount() uint32 { + if m != nil { + return m.DroppedAttributesCount + } + return 0 +} + +func (m *LogRecord) GetFlags() uint32 { + if m != nil { + return m.Flags + } + return 0 +} + +func init() { + proto.RegisterEnum("opentelemetry.proto.logs.v1.SeverityNumber", SeverityNumber_name, SeverityNumber_value) + proto.RegisterEnum("opentelemetry.proto.logs.v1.LogRecordFlags", LogRecordFlags_name, LogRecordFlags_value) + proto.RegisterType((*ResourceLogs)(nil), "opentelemetry.proto.logs.v1.ResourceLogs") + proto.RegisterType((*InstrumentationLibraryLogs)(nil), "opentelemetry.proto.logs.v1.InstrumentationLibraryLogs") + proto.RegisterType((*LogRecord)(nil), "opentelemetry.proto.logs.v1.LogRecord") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/logs/v1/logs.proto", fileDescriptor_d1c030a3ec7e961e) +} + +var fileDescriptor_d1c030a3ec7e961e = []byte{ + // 849 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x96, 0xdf, 0x6e, 0x22, 0x37, + 0x14, 0xc6, 0x71, 0x42, 0x20, 0x71, 0x08, 0xeb, 0xba, 0xd9, 0xec, 0x94, 0x54, 0x04, 0xa5, 0xed, + 0x96, 0xa6, 0x5a, 0x50, 0x06, 0xaa, 0x56, 0xdb, 0xab, 0x21, 0x0c, 0xd1, 0x28, 0x84, 0x44, 0x86, + 0xa4, 0x7f, 0x6e, 0x46, 0x03, 0xb8, 0x68, 0x24, 0xb0, 0xd1, 0x8c, 0x89, 0xc2, 0x5b, 0x54, 0x7d, + 0xa5, 0xde, 0xec, 0x55, 0xbb, 0x57, 0x55, 0xd5, 0x8b, 0x55, 0x95, 0x3c, 0x48, 0x2b, 0x9b, 0x81, + 0x5d, 0xd0, 0x98, 0xd5, 0x5e, 0xc5, 0x73, 0x7e, 0xe7, 0xfb, 0xce, 0xf1, 0x89, 0x6d, 0x01, 0x9f, + 0xf3, 0x31, 0x65, 0x82, 0x0e, 0xe9, 0x88, 0x8a, 0x60, 0x5a, 0x1e, 0x07, 0x5c, 0xf0, 0xf2, 0x90, + 0x0f, 0xc2, 0xf2, 0xdd, 0xa9, 0xfa, 0x5b, 0x52, 0x21, 0x7c, 0xb8, 0x94, 0x37, 0x0b, 0x96, 0x14, + 0xbf, 0x3b, 0xcd, 0xed, 0x0f, 0xf8, 0x80, 0xcf, 0xa4, 0x72, 0x35, 0xa3, 0xb9, 0x93, 0x38, 0xeb, + 0x1e, 0x1f, 0x8d, 0x38, 0x93, 0xe6, 0xb3, 0x55, 0x94, 0x5b, 0x8a, 0xcb, 0x0d, 0x68, 0xc8, 0x27, + 0x41, 0x8f, 0xca, 0xec, 0xf9, 0x7a, 0x96, 0x7f, 0xfc, 0x17, 0x80, 0x19, 0x12, 0x85, 0x9a, 0x7c, + 0x10, 0xe2, 0x0b, 0xb8, 0x3d, 0x4f, 0x31, 0x40, 0x01, 0x14, 0x77, 0xcd, 0xaf, 0x4a, 0x71, 0x2d, + 0x2f, 0x7c, 0xee, 0x4e, 0x4b, 0x73, 0x83, 0x5a, 0xf2, 0xd5, 0x9b, 0xa3, 0x04, 0x59, 0x18, 0xe0, + 0x29, 0xfc, 0xd4, 0x67, 0xa1, 0x08, 0x26, 0x23, 0xca, 0x84, 0x27, 0x7c, 0xce, 0xdc, 0xa1, 0xdf, + 0x0d, 0xbc, 0x60, 0xea, 0xca, 0x2d, 0x1b, 0x1b, 0x85, 0xcd, 0xe2, 0xae, 0xf9, 0x6d, 0x69, 0xcd, + 0x4c, 0x4a, 0xce, 0xb2, 0x41, 0x73, 0xa6, 0x97, 0xbd, 0x92, 0x9c, 0xaf, 0x65, 0xc7, 0x7f, 0x00, + 0x98, 0xd3, 0x4b, 0xb1, 0x80, 0xcf, 0x34, 0x9d, 0x45, 0xbb, 0xfe, 0x26, 0xb6, 0xa9, 0x68, 0xd6, + 0xda, 0xb6, 0xa2, 0x09, 0x1c, 0xc4, 0x37, 0x86, 0x5f, 0xc2, 0xe4, 0x3b, 0xfb, 0x7e, 0xbe, 0x76, + 0xdf, 0x4d, 0x3e, 0x20, 0xb4, 0xc7, 0x83, 0x3e, 0x51, 0x9a, 0xe3, 0x3f, 0x93, 0x70, 0x67, 0x11, + 0xc3, 0x9f, 0xc3, 0xac, 0xf0, 0x47, 0xd4, 0x9d, 0x30, 0xff, 0xde, 0x65, 0x1e, 0xe3, 0xaa, 0xed, + 0x14, 0xc9, 0xc8, 0xe8, 0x0d, 0xf3, 0xef, 0x5b, 0x1e, 0xe3, 0xb8, 0x03, 0x9f, 0x84, 0xf4, 0x8e, + 0x06, 0xbe, 0x98, 0xba, 0x6c, 0x32, 0xea, 0xd2, 0xc0, 0xd8, 0x28, 0x80, 0x62, 0xd6, 0xfc, 0x7a, + 0x6d, 0xe9, 0x76, 0xa4, 0x69, 0x29, 0x09, 0xc9, 0x86, 0x4b, 0xdf, 0xf8, 0x33, 0xb8, 0xb7, 0x70, + 0x15, 0xf4, 0x5e, 0x18, 0x9b, 0x05, 0x50, 0xdc, 0x21, 0x99, 0x79, 0xb0, 0x43, 0xef, 0x05, 0xc6, + 0x30, 0xc9, 0xbc, 0x11, 0x35, 0x92, 0x8a, 0xa9, 0x35, 0xb6, 0x60, 0xb2, 0xcb, 0xfb, 0x53, 0x63, + 0x4b, 0x4d, 0xf8, 0xcb, 0xf7, 0x4c, 0xd8, 0x62, 0xd3, 0x5b, 0x6f, 0x38, 0x99, 0x9f, 0x2a, 0x25, + 0xc5, 0x97, 0x10, 0x7a, 0x42, 0x04, 0x7e, 0x77, 0x22, 0x68, 0x68, 0xa4, 0xd4, 0x1c, 0xdf, 0x67, + 0x74, 0x41, 0x97, 0x8c, 0xde, 0x31, 0xc0, 0xdf, 0x41, 0xa3, 0x1f, 0xf0, 0xf1, 0x98, 0xf6, 0xdd, + 0xb7, 0x51, 0xb7, 0xc7, 0x27, 0x4c, 0x18, 0xe9, 0x02, 0x28, 0xee, 0x91, 0x83, 0x88, 0x5b, 0x0b, + 0x7c, 0x26, 0x29, 0xde, 0x87, 0x5b, 0xbf, 0x0c, 0xbd, 0x41, 0x68, 0x6c, 0x17, 0x40, 0x31, 0x4d, + 0x66, 0x1f, 0xf8, 0x16, 0x6e, 0x8b, 0xc0, 0xeb, 0x51, 0xd7, 0xef, 0x1b, 0x3b, 0x05, 0x50, 0xcc, + 0xd4, 0xbe, 0x97, 0x35, 0xff, 0x79, 0x73, 0x54, 0x19, 0xf0, 0x95, 0x36, 0x7d, 0x79, 0x89, 0x87, + 0x43, 0xda, 0x13, 0x3c, 0x28, 0xfb, 0x4c, 0xd0, 0x80, 0x79, 0xc3, 0x72, 0xdf, 0x13, 0x5e, 0xa9, + 0x23, 0x3d, 0x9c, 0x3a, 0x49, 0x2b, 0x33, 0xa7, 0x8f, 0xdb, 0x30, 0x1d, 0x8e, 0x3d, 0x26, 0x6d, + 0xa1, 0xb2, 0x7d, 0x19, 0xd9, 0x9a, 0x1f, 0x62, 0xdb, 0x1e, 0x7b, 0xcc, 0xa9, 0x93, 0x94, 0xb4, + 0x72, 0xfa, 0x27, 0xbf, 0x6f, 0xc1, 0xec, 0xf2, 0xbf, 0x1a, 0x1f, 0xc1, 0xc3, 0xb6, 0x7d, 0x6b, + 0x13, 0xa7, 0xf3, 0x93, 0xdb, 0xba, 0xb9, 0xac, 0xd9, 0xc4, 0xbd, 0x69, 0xb5, 0xaf, 0xed, 0x33, + 0xa7, 0xe1, 0xd8, 0x75, 0x94, 0xc0, 0x9f, 0xc0, 0xa7, 0xab, 0x09, 0x1d, 0x62, 0x9d, 0xd9, 0x08, + 0xe0, 0x1c, 0x3c, 0x88, 0x45, 0x26, 0xda, 0xd0, 0xb2, 0x0a, 0xda, 0xd4, 0xb2, 0x2a, 0x4a, 0xc6, + 0x95, 0xab, 0xdb, 0xb5, 0x9b, 0x73, 0xb4, 0x15, 0x27, 0x53, 0xc8, 0x44, 0x29, 0x2d, 0xab, 0xa0, + 0xb4, 0x96, 0x55, 0xd1, 0x36, 0x36, 0xe0, 0xfe, 0x2a, 0x73, 0x5a, 0x8d, 0x2b, 0xb4, 0x13, 0xd7, + 0x88, 0x24, 0x26, 0x82, 0x3a, 0x54, 0x41, 0xbb, 0x3a, 0x54, 0x45, 0x99, 0xb8, 0x52, 0x3f, 0x58, + 0xa4, 0x85, 0xf6, 0xe2, 0x44, 0x92, 0x98, 0x28, 0xab, 0x43, 0x15, 0xf4, 0x44, 0x87, 0xaa, 0x08, + 0xc5, 0x21, 0x9b, 0x90, 0x2b, 0x82, 0x3e, 0x8a, 0x1b, 0x86, 0x42, 0x26, 0xc2, 0x5a, 0x56, 0x41, + 0x1f, 0x6b, 0x59, 0x15, 0xed, 0xc7, 0x95, 0x6b, 0x58, 0x1d, 0xab, 0x89, 0x9e, 0xc6, 0xc9, 0x14, + 0x32, 0xd1, 0x81, 0x96, 0x55, 0xd0, 0x33, 0x2d, 0xab, 0x22, 0xe3, 0xe4, 0x47, 0x98, 0x5d, 0x3c, + 0x8b, 0x0d, 0x75, 0x09, 0x8f, 0xe0, 0x61, 0xf3, 0xea, 0xdc, 0x25, 0xf6, 0xd9, 0x15, 0xa9, 0xbb, + 0x8d, 0xa6, 0x75, 0xbe, 0x72, 0x88, 0xbf, 0x80, 0x85, 0xd5, 0x04, 0x75, 0xe2, 0xd4, 0xb2, 0xed, + 0x5e, 0x5a, 0xed, 0x0b, 0xf4, 0x1f, 0xa8, 0xfd, 0x06, 0x5e, 0x3d, 0xe4, 0xc1, 0xeb, 0x87, 0x3c, + 0xf8, 0xf7, 0x21, 0x0f, 0x7e, 0x7d, 0xcc, 0x27, 0x5e, 0x3f, 0xe6, 0x13, 0x7f, 0x3f, 0xe6, 0x13, + 0x30, 0xef, 0xf3, 0x75, 0x2f, 0x68, 0x4d, 0xbe, 0xd4, 0xe1, 0xb5, 0x0c, 0x5d, 0x83, 0x9f, 0x2f, + 0x3e, 0xe0, 0x8e, 0x96, 0x97, 0x12, 0x5f, 0x28, 0xd7, 0x17, 0x03, 0xca, 0xe6, 0x3f, 0x25, 0xba, + 0x29, 0x15, 0xaa, 0xfc, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x28, 0xc9, 0xea, 0x70, 0x08, 0x00, + 0x00, +} + +func (m *ResourceLogs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourceLogs) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceLogs) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.InstrumentationLibraryLogs) > 0 { + for iNdEx := len(m.InstrumentationLibraryLogs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.InstrumentationLibraryLogs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *InstrumentationLibraryLogs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InstrumentationLibraryLogs) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InstrumentationLibraryLogs) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Logs) > 0 { + for iNdEx := len(m.Logs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Logs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.InstrumentationLibrary.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *LogRecord) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LogRecord) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *LogRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.SpanId.Size() + i -= size + if _, err := m.SpanId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + { + size := m.TraceId.Size() + i -= size + if _, err := m.TraceId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + if m.Flags != 0 { + i -= 4 + encoding_binary.LittleEndian.PutUint32(dAtA[i:], uint32(m.Flags)) + i-- + dAtA[i] = 0x45 + } + if m.DroppedAttributesCount != 0 { + i = encodeVarintLogs(dAtA, i, uint64(m.DroppedAttributesCount)) + i-- + dAtA[i] = 0x38 + } + if len(m.Attributes) > 0 { + for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + { + size, err := m.Body.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintLogs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintLogs(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x22 + } + if len(m.SeverityText) > 0 { + i -= len(m.SeverityText) + copy(dAtA[i:], m.SeverityText) + i = encodeVarintLogs(dAtA, i, uint64(len(m.SeverityText))) + i-- + dAtA[i] = 0x1a + } + if m.SeverityNumber != 0 { + i = encodeVarintLogs(dAtA, i, uint64(m.SeverityNumber)) + i-- + dAtA[i] = 0x10 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x9 + } + return len(dAtA) - i, nil +} + +func encodeVarintLogs(dAtA []byte, offset int, v uint64) int { + offset -= sovLogs(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ResourceLogs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Resource.Size() + n += 1 + l + sovLogs(uint64(l)) + if len(m.InstrumentationLibraryLogs) > 0 { + for _, e := range m.InstrumentationLibraryLogs { + l = e.Size() + n += 1 + l + sovLogs(uint64(l)) + } + } + return n +} + +func (m *InstrumentationLibraryLogs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.InstrumentationLibrary.Size() + n += 1 + l + sovLogs(uint64(l)) + if len(m.Logs) > 0 { + for _, e := range m.Logs { + l = e.Size() + n += 1 + l + sovLogs(uint64(l)) + } + } + return n +} + +func (m *LogRecord) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TimeUnixNano != 0 { + n += 9 + } + if m.SeverityNumber != 0 { + n += 1 + sovLogs(uint64(m.SeverityNumber)) + } + l = len(m.SeverityText) + if l > 0 { + n += 1 + l + sovLogs(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovLogs(uint64(l)) + } + l = m.Body.Size() + n += 1 + l + sovLogs(uint64(l)) + if len(m.Attributes) > 0 { + for _, e := range m.Attributes { + l = e.Size() + n += 1 + l + sovLogs(uint64(l)) + } + } + if m.DroppedAttributesCount != 0 { + n += 1 + sovLogs(uint64(m.DroppedAttributesCount)) + } + if m.Flags != 0 { + n += 5 + } + l = m.TraceId.Size() + n += 1 + l + sovLogs(uint64(l)) + l = m.SpanId.Size() + n += 1 + l + sovLogs(uint64(l)) + return n +} + +func sovLogs(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozLogs(x uint64) (n int) { + return sovLogs(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ResourceLogs) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResourceLogs: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResourceLogs: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstrumentationLibraryLogs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstrumentationLibraryLogs = append(m.InstrumentationLibraryLogs, &InstrumentationLibraryLogs{}) + if err := m.InstrumentationLibraryLogs[len(m.InstrumentationLibraryLogs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLogs(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLogs + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthLogs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InstrumentationLibraryLogs) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InstrumentationLibraryLogs: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InstrumentationLibraryLogs: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstrumentationLibrary", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InstrumentationLibrary.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Logs = append(m.Logs, &LogRecord{}) + if err := m.Logs[len(m.Logs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLogs(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLogs + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthLogs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LogRecord) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LogRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LogRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SeverityNumber", wireType) + } + m.SeverityNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SeverityNumber |= SeverityNumber(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SeverityText", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SeverityText = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Body.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attributes = append(m.Attributes, v11.KeyValue{}) + if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType) + } + m.DroppedAttributesCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedAttributesCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) + } + m.Flags = 0 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + m.Flags = uint32(encoding_binary.LittleEndian.Uint32(dAtA[iNdEx:])) + iNdEx += 4 + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TraceId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLogs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLogs + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthLogs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SpanId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLogs(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLogs + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthLogs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLogs(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLogs + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLogs + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLogs + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthLogs + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupLogs + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthLogs + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthLogs = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLogs = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupLogs = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/metrics/v1/metrics.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/metrics/v1/metrics.pb.go new file mode 100644 index 00000000000..963162699df --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/metrics/v1/metrics.pb.go @@ -0,0 +1,6374 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/metrics/v1/metrics.proto + +package v1 + +import ( + encoding_binary "encoding/binary" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + + go_opentelemetry_io_collector_internal_data "go.opentelemetry.io/collector/internal/data" + v11 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// AggregationTemporality defines how a metric aggregator reports aggregated +// values. It describes how those values relate to the time interval over +// which they are aggregated. +type AggregationTemporality int32 + +const ( + // UNSPECIFIED is the default AggregationTemporality, it MUST not be used. + AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED AggregationTemporality = 0 + // DELTA is an AggregationTemporality for a metric aggregator which reports + // changes since last report time. Successive metrics contain aggregation of + // values from continuous and non-overlapping intervals. + // + // The values for a DELTA metric are based only on the time interval + // associated with one measurement cycle. There is no dependency on + // previous measurements like is the case for CUMULATIVE metrics. + // + // For example, consider a system measuring the number of requests that + // it receives and reports the sum of these requests every second as a + // DELTA metric: + // + // 1. The system starts receiving at time=t_0. + // 2. A request is received, the system measures 1 request. + // 3. A request is received, the system measures 1 request. + // 4. A request is received, the system measures 1 request. + // 5. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0 to + // t_0+1 with a value of 3. + // 6. A request is received, the system measures 1 request. + // 7. A request is received, the system measures 1 request. + // 8. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0+1 to + // t_0+2 with a value of 2. + AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA AggregationTemporality = 1 + // CUMULATIVE is an AggregationTemporality for a metric aggregator which + // reports changes since a fixed start time. This means that current values + // of a CUMULATIVE metric depend on all previous measurements since the + // start time. Because of this, the sender is required to retain this state + // in some form. If this state is lost or invalidated, the CUMULATIVE metric + // values MUST be reset and a new fixed start time following the last + // reported measurement time sent MUST be used. + // + // For example, consider a system measuring the number of requests that + // it receives and reports the sum of these requests every second as a + // CUMULATIVE metric: + // + // 1. The system starts receiving at time=t_0. + // 2. A request is received, the system measures 1 request. + // 3. A request is received, the system measures 1 request. + // 4. A request is received, the system measures 1 request. + // 5. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0 to + // t_0+1 with a value of 3. + // 6. A request is received, the system measures 1 request. + // 7. A request is received, the system measures 1 request. + // 8. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0 to + // t_0+2 with a value of 5. + // 9. The system experiences a fault and loses state. + // 10. The system recovers and resumes receiving at time=t_1. + // 11. A request is received, the system measures 1 request. + // 12. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_1 to + // t_0+1 with a value of 1. + // + // Note: Even though, when reporting changes since last report time, using + // CUMULATIVE is valid, it is not recommended. This may cause problems for + // systems that do not use start_time to determine when the aggregation + // value was reset (e.g. Prometheus). + AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE AggregationTemporality = 2 +) + +var AggregationTemporality_name = map[int32]string{ + 0: "AGGREGATION_TEMPORALITY_UNSPECIFIED", + 1: "AGGREGATION_TEMPORALITY_DELTA", + 2: "AGGREGATION_TEMPORALITY_CUMULATIVE", +} + +var AggregationTemporality_value = map[string]int32{ + "AGGREGATION_TEMPORALITY_UNSPECIFIED": 0, + "AGGREGATION_TEMPORALITY_DELTA": 1, + "AGGREGATION_TEMPORALITY_CUMULATIVE": 2, +} + +func (x AggregationTemporality) String() string { + return proto.EnumName(AggregationTemporality_name, int32(x)) +} + +func (AggregationTemporality) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{0} +} + +// A collection of InstrumentationLibraryMetrics from a Resource. +type ResourceMetrics struct { + // The resource for the metrics in this message. + // If this field is not set then no resource info is known. + Resource v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource"` + // A list of metrics that originate from a resource. + InstrumentationLibraryMetrics []*InstrumentationLibraryMetrics `protobuf:"bytes,2,rep,name=instrumentation_library_metrics,json=instrumentationLibraryMetrics,proto3" json:"instrumentation_library_metrics,omitempty"` +} + +func (m *ResourceMetrics) Reset() { *m = ResourceMetrics{} } +func (m *ResourceMetrics) String() string { return proto.CompactTextString(m) } +func (*ResourceMetrics) ProtoMessage() {} +func (*ResourceMetrics) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{0} +} +func (m *ResourceMetrics) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResourceMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResourceMetrics.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ResourceMetrics) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResourceMetrics.Merge(m, src) +} +func (m *ResourceMetrics) XXX_Size() int { + return m.Size() +} +func (m *ResourceMetrics) XXX_DiscardUnknown() { + xxx_messageInfo_ResourceMetrics.DiscardUnknown(m) +} + +var xxx_messageInfo_ResourceMetrics proto.InternalMessageInfo + +func (m *ResourceMetrics) GetResource() v1.Resource { + if m != nil { + return m.Resource + } + return v1.Resource{} +} + +func (m *ResourceMetrics) GetInstrumentationLibraryMetrics() []*InstrumentationLibraryMetrics { + if m != nil { + return m.InstrumentationLibraryMetrics + } + return nil +} + +// A collection of Metrics produced by an InstrumentationLibrary. +type InstrumentationLibraryMetrics struct { + // The instrumentation library information for the metrics in this message. + // If this field is not set then no library info is known. + InstrumentationLibrary v11.InstrumentationLibrary `protobuf:"bytes,1,opt,name=instrumentation_library,json=instrumentationLibrary,proto3" json:"instrumentation_library"` + // A list of metrics that originate from an instrumentation library. + Metrics []*Metric `protobuf:"bytes,2,rep,name=metrics,proto3" json:"metrics,omitempty"` +} + +func (m *InstrumentationLibraryMetrics) Reset() { *m = InstrumentationLibraryMetrics{} } +func (m *InstrumentationLibraryMetrics) String() string { return proto.CompactTextString(m) } +func (*InstrumentationLibraryMetrics) ProtoMessage() {} +func (*InstrumentationLibraryMetrics) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{1} +} +func (m *InstrumentationLibraryMetrics) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InstrumentationLibraryMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InstrumentationLibraryMetrics.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InstrumentationLibraryMetrics) XXX_Merge(src proto.Message) { + xxx_messageInfo_InstrumentationLibraryMetrics.Merge(m, src) +} +func (m *InstrumentationLibraryMetrics) XXX_Size() int { + return m.Size() +} +func (m *InstrumentationLibraryMetrics) XXX_DiscardUnknown() { + xxx_messageInfo_InstrumentationLibraryMetrics.DiscardUnknown(m) +} + +var xxx_messageInfo_InstrumentationLibraryMetrics proto.InternalMessageInfo + +func (m *InstrumentationLibraryMetrics) GetInstrumentationLibrary() v11.InstrumentationLibrary { + if m != nil { + return m.InstrumentationLibrary + } + return v11.InstrumentationLibrary{} +} + +func (m *InstrumentationLibraryMetrics) GetMetrics() []*Metric { + if m != nil { + return m.Metrics + } + return nil +} + +// Defines a Metric which has one or more timeseries. +// +// The data model and relation between entities is shown in the +// diagram below. Here, "DataPoint" is the term used to refer to any +// one of the specific data point value types, and "points" is the term used +// to refer to any one of the lists of points contained in the Metric. +// +// - Metric is composed of a metadata and data. +// - Metadata part contains a name, description, unit. +// - Data is one of the possible types (Gauge, Sum, Histogram, etc.). +// - DataPoint contains timestamps, labels, and one of the possible value type +// fields. +// +// Metric +// +------------+ +// |name | +// |description | +// |unit | +------------------------------------+ +// |data |---> |Gauge, Sum, Histogram, Summary, ... | +// +------------+ +------------------------------------+ +// +// Data [One of Gauge, Sum, Histogram, Summary, ...] +// +-----------+ +// |... | // Metadata about the Data. +// |points |--+ +// +-----------+ | +// | +---------------------------+ +// | |DataPoint 1 | +// v |+------+------+ +------+ | +// +-----+ ||label |label |...|label | | +// | 1 |-->||value1|value2|...|valueN| | +// +-----+ |+------+------+ +------+ | +// | . | |+-----+ | +// | . | ||value| | +// | . | |+-----+ | +// | . | +---------------------------+ +// | . | . +// | . | . +// | . | . +// | . | +---------------------------+ +// | . | |DataPoint M | +// +-----+ |+------+------+ +------+ | +// | M |-->||label |label |...|label | | +// +-----+ ||value1|value2|...|valueN| | +// |+------+------+ +------+ | +// |+-----+ | +// ||value| | +// |+-----+ | +// +---------------------------+ +// +// All DataPoint types have three common fields: +// - Labels zero or more key-value pairs associated with the data point. +// - StartTimeUnixNano MUST be set to the start of the interval when the data's +// type includes an AggregationTemporality. This field is not set otherwise. +// - TimeUnixNano MUST be set to: +// - the moment when an aggregation is reported (independent of the +// aggregation temporality). +// - the instantaneous time of the event. +type Metric struct { + // name of the metric, including its DNS name prefix. It must be unique. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // description of the metric, which can be used in documentation. + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // unit in which the metric value is reported. Follows the format + // described by http://unitsofmeasure.org/ucum.html. + Unit string `protobuf:"bytes,3,opt,name=unit,proto3" json:"unit,omitempty"` + // Data determines the aggregation type (if any) of the metric, what is the + // reported value type for the data points, as well as the relatationship to + // the time interval over which they are reported. + // + // TODO: Update table after the decision on: + // https://github.com/open-telemetry/opentelemetry-specification/issues/731. + // By default, metrics recording using the OpenTelemetry API are exported as + // (the table does not include MeasurementValueType to avoid extra rows): + // + // Instrument Type + // ---------------------------------------------- + // Counter Sum(aggregation_temporality=delta;is_monotonic=true) + // UpDownCounter Sum(aggregation_temporality=delta;is_monotonic=false) + // ValueRecorder TBD + // SumObserver Sum(aggregation_temporality=cumulative;is_monotonic=true) + // UpDownSumObserver Sum(aggregation_temporality=cumulative;is_monotonic=false) + // ValueObserver Gauge() + // + // Types that are valid to be assigned to Data: + // *Metric_IntGauge + // *Metric_DoubleGauge + // *Metric_IntSum + // *Metric_DoubleSum + // *Metric_IntHistogram + // *Metric_DoubleHistogram + // *Metric_DoubleSummary + Data isMetric_Data `protobuf_oneof:"data"` +} + +func (m *Metric) Reset() { *m = Metric{} } +func (m *Metric) String() string { return proto.CompactTextString(m) } +func (*Metric) ProtoMessage() {} +func (*Metric) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{2} +} +func (m *Metric) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Metric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Metric.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Metric) XXX_Merge(src proto.Message) { + xxx_messageInfo_Metric.Merge(m, src) +} +func (m *Metric) XXX_Size() int { + return m.Size() +} +func (m *Metric) XXX_DiscardUnknown() { + xxx_messageInfo_Metric.DiscardUnknown(m) +} + +var xxx_messageInfo_Metric proto.InternalMessageInfo + +type isMetric_Data interface { + isMetric_Data() + MarshalTo([]byte) (int, error) + Size() int +} + +type Metric_IntGauge struct { + IntGauge *IntGauge `protobuf:"bytes,4,opt,name=int_gauge,json=intGauge,proto3,oneof" json:"int_gauge,omitempty"` +} +type Metric_DoubleGauge struct { + DoubleGauge *DoubleGauge `protobuf:"bytes,5,opt,name=double_gauge,json=doubleGauge,proto3,oneof" json:"double_gauge,omitempty"` +} +type Metric_IntSum struct { + IntSum *IntSum `protobuf:"bytes,6,opt,name=int_sum,json=intSum,proto3,oneof" json:"int_sum,omitempty"` +} +type Metric_DoubleSum struct { + DoubleSum *DoubleSum `protobuf:"bytes,7,opt,name=double_sum,json=doubleSum,proto3,oneof" json:"double_sum,omitempty"` +} +type Metric_IntHistogram struct { + IntHistogram *IntHistogram `protobuf:"bytes,8,opt,name=int_histogram,json=intHistogram,proto3,oneof" json:"int_histogram,omitempty"` +} +type Metric_DoubleHistogram struct { + DoubleHistogram *DoubleHistogram `protobuf:"bytes,9,opt,name=double_histogram,json=doubleHistogram,proto3,oneof" json:"double_histogram,omitempty"` +} +type Metric_DoubleSummary struct { + DoubleSummary *DoubleSummary `protobuf:"bytes,11,opt,name=double_summary,json=doubleSummary,proto3,oneof" json:"double_summary,omitempty"` +} + +func (*Metric_IntGauge) isMetric_Data() {} +func (*Metric_DoubleGauge) isMetric_Data() {} +func (*Metric_IntSum) isMetric_Data() {} +func (*Metric_DoubleSum) isMetric_Data() {} +func (*Metric_IntHistogram) isMetric_Data() {} +func (*Metric_DoubleHistogram) isMetric_Data() {} +func (*Metric_DoubleSummary) isMetric_Data() {} + +func (m *Metric) GetData() isMetric_Data { + if m != nil { + return m.Data + } + return nil +} + +func (m *Metric) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Metric) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *Metric) GetUnit() string { + if m != nil { + return m.Unit + } + return "" +} + +func (m *Metric) GetIntGauge() *IntGauge { + if x, ok := m.GetData().(*Metric_IntGauge); ok { + return x.IntGauge + } + return nil +} + +func (m *Metric) GetDoubleGauge() *DoubleGauge { + if x, ok := m.GetData().(*Metric_DoubleGauge); ok { + return x.DoubleGauge + } + return nil +} + +func (m *Metric) GetIntSum() *IntSum { + if x, ok := m.GetData().(*Metric_IntSum); ok { + return x.IntSum + } + return nil +} + +func (m *Metric) GetDoubleSum() *DoubleSum { + if x, ok := m.GetData().(*Metric_DoubleSum); ok { + return x.DoubleSum + } + return nil +} + +func (m *Metric) GetIntHistogram() *IntHistogram { + if x, ok := m.GetData().(*Metric_IntHistogram); ok { + return x.IntHistogram + } + return nil +} + +func (m *Metric) GetDoubleHistogram() *DoubleHistogram { + if x, ok := m.GetData().(*Metric_DoubleHistogram); ok { + return x.DoubleHistogram + } + return nil +} + +func (m *Metric) GetDoubleSummary() *DoubleSummary { + if x, ok := m.GetData().(*Metric_DoubleSummary); ok { + return x.DoubleSummary + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*Metric) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*Metric_IntGauge)(nil), + (*Metric_DoubleGauge)(nil), + (*Metric_IntSum)(nil), + (*Metric_DoubleSum)(nil), + (*Metric_IntHistogram)(nil), + (*Metric_DoubleHistogram)(nil), + (*Metric_DoubleSummary)(nil), + } +} + +// Gauge represents the type of a int scalar metric that always exports the +// "current value" for every data point. It should be used for an "unknown" +// aggregation. +// +// A Gauge does not support different aggregation temporalities. Given the +// aggregation is unknown, points cannot be combined using the same +// aggregation, regardless of aggregation temporalities. Therefore, +// AggregationTemporality is not included. Consequently, this also means +// "StartTimeUnixNano" is ignored for all data points. +type IntGauge struct { + DataPoints []*IntDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` +} + +func (m *IntGauge) Reset() { *m = IntGauge{} } +func (m *IntGauge) String() string { return proto.CompactTextString(m) } +func (*IntGauge) ProtoMessage() {} +func (*IntGauge) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{3} +} +func (m *IntGauge) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntGauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntGauge.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntGauge) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntGauge.Merge(m, src) +} +func (m *IntGauge) XXX_Size() int { + return m.Size() +} +func (m *IntGauge) XXX_DiscardUnknown() { + xxx_messageInfo_IntGauge.DiscardUnknown(m) +} + +var xxx_messageInfo_IntGauge proto.InternalMessageInfo + +func (m *IntGauge) GetDataPoints() []*IntDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +// Gauge represents the type of a double scalar metric that always exports the +// "current value" for every data point. It should be used for an "unknown" +// aggregation. +// +// A Gauge does not support different aggregation temporalities. Given the +// aggregation is unknown, points cannot be combined using the same +// aggregation, regardless of aggregation temporalities. Therefore, +// AggregationTemporality is not included. Consequently, this also means +// "StartTimeUnixNano" is ignored for all data points. +type DoubleGauge struct { + DataPoints []*DoubleDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` +} + +func (m *DoubleGauge) Reset() { *m = DoubleGauge{} } +func (m *DoubleGauge) String() string { return proto.CompactTextString(m) } +func (*DoubleGauge) ProtoMessage() {} +func (*DoubleGauge) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{4} +} +func (m *DoubleGauge) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleGauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleGauge.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleGauge) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleGauge.Merge(m, src) +} +func (m *DoubleGauge) XXX_Size() int { + return m.Size() +} +func (m *DoubleGauge) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleGauge.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleGauge proto.InternalMessageInfo + +func (m *DoubleGauge) GetDataPoints() []*DoubleDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +// Sum represents the type of a numeric int scalar metric that is calculated as +// a sum of all reported measurements over a time interval. +type IntSum struct { + DataPoints []*IntDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` + // aggregation_temporality describes if the aggregator reports delta changes + // since last report time, or cumulative changes since a fixed start time. + AggregationTemporality AggregationTemporality `protobuf:"varint,2,opt,name=aggregation_temporality,json=aggregationTemporality,proto3,enum=opentelemetry.proto.metrics.v1.AggregationTemporality" json:"aggregation_temporality,omitempty"` + // If "true" means that the sum is monotonic. + IsMonotonic bool `protobuf:"varint,3,opt,name=is_monotonic,json=isMonotonic,proto3" json:"is_monotonic,omitempty"` +} + +func (m *IntSum) Reset() { *m = IntSum{} } +func (m *IntSum) String() string { return proto.CompactTextString(m) } +func (*IntSum) ProtoMessage() {} +func (*IntSum) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{5} +} +func (m *IntSum) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntSum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntSum.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntSum) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntSum.Merge(m, src) +} +func (m *IntSum) XXX_Size() int { + return m.Size() +} +func (m *IntSum) XXX_DiscardUnknown() { + xxx_messageInfo_IntSum.DiscardUnknown(m) +} + +var xxx_messageInfo_IntSum proto.InternalMessageInfo + +func (m *IntSum) GetDataPoints() []*IntDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +func (m *IntSum) GetAggregationTemporality() AggregationTemporality { + if m != nil { + return m.AggregationTemporality + } + return AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED +} + +func (m *IntSum) GetIsMonotonic() bool { + if m != nil { + return m.IsMonotonic + } + return false +} + +// Sum represents the type of a numeric double scalar metric that is calculated +// as a sum of all reported measurements over a time interval. +type DoubleSum struct { + DataPoints []*DoubleDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` + // aggregation_temporality describes if the aggregator reports delta changes + // since last report time, or cumulative changes since a fixed start time. + AggregationTemporality AggregationTemporality `protobuf:"varint,2,opt,name=aggregation_temporality,json=aggregationTemporality,proto3,enum=opentelemetry.proto.metrics.v1.AggregationTemporality" json:"aggregation_temporality,omitempty"` + // If "true" means that the sum is monotonic. + IsMonotonic bool `protobuf:"varint,3,opt,name=is_monotonic,json=isMonotonic,proto3" json:"is_monotonic,omitempty"` +} + +func (m *DoubleSum) Reset() { *m = DoubleSum{} } +func (m *DoubleSum) String() string { return proto.CompactTextString(m) } +func (*DoubleSum) ProtoMessage() {} +func (*DoubleSum) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{6} +} +func (m *DoubleSum) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleSum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleSum.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleSum) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleSum.Merge(m, src) +} +func (m *DoubleSum) XXX_Size() int { + return m.Size() +} +func (m *DoubleSum) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleSum.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleSum proto.InternalMessageInfo + +func (m *DoubleSum) GetDataPoints() []*DoubleDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +func (m *DoubleSum) GetAggregationTemporality() AggregationTemporality { + if m != nil { + return m.AggregationTemporality + } + return AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED +} + +func (m *DoubleSum) GetIsMonotonic() bool { + if m != nil { + return m.IsMonotonic + } + return false +} + +// Represents the type of a metric that is calculated by aggregating as a +// Histogram of all reported int measurements over a time interval. +type IntHistogram struct { + DataPoints []*IntHistogramDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` + // aggregation_temporality describes if the aggregator reports delta changes + // since last report time, or cumulative changes since a fixed start time. + AggregationTemporality AggregationTemporality `protobuf:"varint,2,opt,name=aggregation_temporality,json=aggregationTemporality,proto3,enum=opentelemetry.proto.metrics.v1.AggregationTemporality" json:"aggregation_temporality,omitempty"` +} + +func (m *IntHistogram) Reset() { *m = IntHistogram{} } +func (m *IntHistogram) String() string { return proto.CompactTextString(m) } +func (*IntHistogram) ProtoMessage() {} +func (*IntHistogram) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{7} +} +func (m *IntHistogram) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntHistogram) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntHistogram.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntHistogram) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntHistogram.Merge(m, src) +} +func (m *IntHistogram) XXX_Size() int { + return m.Size() +} +func (m *IntHistogram) XXX_DiscardUnknown() { + xxx_messageInfo_IntHistogram.DiscardUnknown(m) +} + +var xxx_messageInfo_IntHistogram proto.InternalMessageInfo + +func (m *IntHistogram) GetDataPoints() []*IntHistogramDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +func (m *IntHistogram) GetAggregationTemporality() AggregationTemporality { + if m != nil { + return m.AggregationTemporality + } + return AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED +} + +// Represents the type of a metric that is calculated by aggregating as a +// Histogram of all reported double measurements over a time interval. +type DoubleHistogram struct { + DataPoints []*DoubleHistogramDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` + // aggregation_temporality describes if the aggregator reports delta changes + // since last report time, or cumulative changes since a fixed start time. + AggregationTemporality AggregationTemporality `protobuf:"varint,2,opt,name=aggregation_temporality,json=aggregationTemporality,proto3,enum=opentelemetry.proto.metrics.v1.AggregationTemporality" json:"aggregation_temporality,omitempty"` +} + +func (m *DoubleHistogram) Reset() { *m = DoubleHistogram{} } +func (m *DoubleHistogram) String() string { return proto.CompactTextString(m) } +func (*DoubleHistogram) ProtoMessage() {} +func (*DoubleHistogram) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{8} +} +func (m *DoubleHistogram) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleHistogram) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleHistogram.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleHistogram) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleHistogram.Merge(m, src) +} +func (m *DoubleHistogram) XXX_Size() int { + return m.Size() +} +func (m *DoubleHistogram) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleHistogram.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleHistogram proto.InternalMessageInfo + +func (m *DoubleHistogram) GetDataPoints() []*DoubleHistogramDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +func (m *DoubleHistogram) GetAggregationTemporality() AggregationTemporality { + if m != nil { + return m.AggregationTemporality + } + return AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED +} + +// DoubleSummary metric data are used to convey quantile summaries, +// a Prometheus (see: https://prometheus.io/docs/concepts/metric_types/#summary) +// and OpenMetrics (see: https://github.com/OpenObservability/OpenMetrics/blob/4dbf6075567ab43296eed941037c12951faafb92/protos/prometheus.proto#L45) +// data type. These data points cannot always be merged in a meaningful way. +// While they can be useful in some applications, histogram data points are +// recommended for new applications. +type DoubleSummary struct { + DataPoints []*DoubleSummaryDataPoint `protobuf:"bytes,1,rep,name=data_points,json=dataPoints,proto3" json:"data_points,omitempty"` +} + +func (m *DoubleSummary) Reset() { *m = DoubleSummary{} } +func (m *DoubleSummary) String() string { return proto.CompactTextString(m) } +func (*DoubleSummary) ProtoMessage() {} +func (*DoubleSummary) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{9} +} +func (m *DoubleSummary) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleSummary.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleSummary) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleSummary.Merge(m, src) +} +func (m *DoubleSummary) XXX_Size() int { + return m.Size() +} +func (m *DoubleSummary) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleSummary.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleSummary proto.InternalMessageInfo + +func (m *DoubleSummary) GetDataPoints() []*DoubleSummaryDataPoint { + if m != nil { + return m.DataPoints + } + return nil +} + +// IntDataPoint is a single data point in a timeseries that describes the +// time-varying values of a int64 metric. +type IntDataPoint struct { + // The set of labels that uniquely identify this timeseries. + Labels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels"` + // start_time_unix_nano is the last time when the aggregation value was reset + // to "zero". For some metric types this is ignored, see data types for more + // details. + // + // The aggregation value is over the time interval (start_time_unix_nano, + // time_unix_nano]. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + // + // Value of 0 indicates that the timestamp is unspecified. In that case the + // timestamp may be decided by the backend. + StartTimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // time_unix_nano is the moment when this aggregation value was reported. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,3,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // value itself. + Value int64 `protobuf:"fixed64,4,opt,name=value,proto3" json:"value,omitempty"` + // (Optional) List of exemplars collected from + // measurements that were used to form the data point + Exemplars []*IntExemplar `protobuf:"bytes,5,rep,name=exemplars,proto3" json:"exemplars,omitempty"` +} + +func (m *IntDataPoint) Reset() { *m = IntDataPoint{} } +func (m *IntDataPoint) String() string { return proto.CompactTextString(m) } +func (*IntDataPoint) ProtoMessage() {} +func (*IntDataPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{10} +} +func (m *IntDataPoint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntDataPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntDataPoint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntDataPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntDataPoint.Merge(m, src) +} +func (m *IntDataPoint) XXX_Size() int { + return m.Size() +} +func (m *IntDataPoint) XXX_DiscardUnknown() { + xxx_messageInfo_IntDataPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_IntDataPoint proto.InternalMessageInfo + +func (m *IntDataPoint) GetLabels() []v11.StringKeyValue { + if m != nil { + return m.Labels + } + return nil +} + +func (m *IntDataPoint) GetStartTimeUnixNano() uint64 { + if m != nil { + return m.StartTimeUnixNano + } + return 0 +} + +func (m *IntDataPoint) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *IntDataPoint) GetValue() int64 { + if m != nil { + return m.Value + } + return 0 +} + +func (m *IntDataPoint) GetExemplars() []*IntExemplar { + if m != nil { + return m.Exemplars + } + return nil +} + +// DoubleDataPoint is a single data point in a timeseries that describes the +// time-varying value of a double metric. +type DoubleDataPoint struct { + // The set of labels that uniquely identify this timeseries. + Labels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels"` + // start_time_unix_nano is the last time when the aggregation value was reset + // to "zero". For some metric types this is ignored, see data types for more + // details. + // + // The aggregation value is over the time interval (start_time_unix_nano, + // time_unix_nano]. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + // + // Value of 0 indicates that the timestamp is unspecified. In that case the + // timestamp may be decided by the backend. + StartTimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // time_unix_nano is the moment when this aggregation value was reported. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,3,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // value itself. + Value float64 `protobuf:"fixed64,4,opt,name=value,proto3" json:"value,omitempty"` + // (Optional) List of exemplars collected from + // measurements that were used to form the data point + Exemplars []*DoubleExemplar `protobuf:"bytes,5,rep,name=exemplars,proto3" json:"exemplars,omitempty"` +} + +func (m *DoubleDataPoint) Reset() { *m = DoubleDataPoint{} } +func (m *DoubleDataPoint) String() string { return proto.CompactTextString(m) } +func (*DoubleDataPoint) ProtoMessage() {} +func (*DoubleDataPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{11} +} +func (m *DoubleDataPoint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleDataPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleDataPoint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleDataPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleDataPoint.Merge(m, src) +} +func (m *DoubleDataPoint) XXX_Size() int { + return m.Size() +} +func (m *DoubleDataPoint) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleDataPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleDataPoint proto.InternalMessageInfo + +func (m *DoubleDataPoint) GetLabels() []v11.StringKeyValue { + if m != nil { + return m.Labels + } + return nil +} + +func (m *DoubleDataPoint) GetStartTimeUnixNano() uint64 { + if m != nil { + return m.StartTimeUnixNano + } + return 0 +} + +func (m *DoubleDataPoint) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *DoubleDataPoint) GetValue() float64 { + if m != nil { + return m.Value + } + return 0 +} + +func (m *DoubleDataPoint) GetExemplars() []*DoubleExemplar { + if m != nil { + return m.Exemplars + } + return nil +} + +// IntHistogramDataPoint is a single data point in a timeseries that describes +// the time-varying values of a Histogram of int values. A Histogram contains +// summary statistics for a population of values, it may optionally contain +// the distribution of those values across a set of buckets. +type IntHistogramDataPoint struct { + // The set of labels that uniquely identify this timeseries. + Labels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels"` + // start_time_unix_nano is the last time when the aggregation value was reset + // to "zero". For some metric types this is ignored, see data types for more + // details. + // + // The aggregation value is over the time interval (start_time_unix_nano, + // time_unix_nano]. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + // + // Value of 0 indicates that the timestamp is unspecified. In that case the + // timestamp may be decided by the backend. + StartTimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // time_unix_nano is the moment when this aggregation value was reported. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,3,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // count is the number of values in the population. Must be non-negative. This + // value must be equal to the sum of the "count" fields in buckets if a + // histogram is provided. + Count uint64 `protobuf:"fixed64,4,opt,name=count,proto3" json:"count,omitempty"` + // sum of the values in the population. If count is zero then this field + // must be zero. This value must be equal to the sum of the "sum" fields in + // buckets if a histogram is provided. + Sum int64 `protobuf:"fixed64,5,opt,name=sum,proto3" json:"sum,omitempty"` + // bucket_counts is an optional field contains the count values of histogram + // for each bucket. + // + // The sum of the bucket_counts must equal the value in the count field. + // + // The number of elements in bucket_counts array must be by one greater than + // the number of elements in explicit_bounds array. + BucketCounts []uint64 `protobuf:"fixed64,6,rep,packed,name=bucket_counts,json=bucketCounts,proto3" json:"bucket_counts,omitempty"` + // explicit_bounds specifies buckets with explicitly defined bounds for values. + // The bucket boundaries are described by "bounds" field. + // + // This defines size(bounds) + 1 (= N) buckets. The boundaries for bucket + // at index i are: + // + // (-infinity, bounds[i]) for i == 0 + // [bounds[i-1], bounds[i]) for 0 < i < N-1 + // [bounds[i], +infinity) for i == N-1 + // The values in bounds array must be strictly increasing. + // + // Note: only [a, b) intervals are currently supported for each bucket except the first one. + // If we decide to also support (a, b] intervals we should add support for these by defining + // a boolean value which decides what type of intervals to use. + ExplicitBounds []float64 `protobuf:"fixed64,7,rep,packed,name=explicit_bounds,json=explicitBounds,proto3" json:"explicit_bounds,omitempty"` + // (Optional) List of exemplars collected from + // measurements that were used to form the data point + Exemplars []*IntExemplar `protobuf:"bytes,8,rep,name=exemplars,proto3" json:"exemplars,omitempty"` +} + +func (m *IntHistogramDataPoint) Reset() { *m = IntHistogramDataPoint{} } +func (m *IntHistogramDataPoint) String() string { return proto.CompactTextString(m) } +func (*IntHistogramDataPoint) ProtoMessage() {} +func (*IntHistogramDataPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{12} +} +func (m *IntHistogramDataPoint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntHistogramDataPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntHistogramDataPoint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntHistogramDataPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntHistogramDataPoint.Merge(m, src) +} +func (m *IntHistogramDataPoint) XXX_Size() int { + return m.Size() +} +func (m *IntHistogramDataPoint) XXX_DiscardUnknown() { + xxx_messageInfo_IntHistogramDataPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_IntHistogramDataPoint proto.InternalMessageInfo + +func (m *IntHistogramDataPoint) GetLabels() []v11.StringKeyValue { + if m != nil { + return m.Labels + } + return nil +} + +func (m *IntHistogramDataPoint) GetStartTimeUnixNano() uint64 { + if m != nil { + return m.StartTimeUnixNano + } + return 0 +} + +func (m *IntHistogramDataPoint) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *IntHistogramDataPoint) GetCount() uint64 { + if m != nil { + return m.Count + } + return 0 +} + +func (m *IntHistogramDataPoint) GetSum() int64 { + if m != nil { + return m.Sum + } + return 0 +} + +func (m *IntHistogramDataPoint) GetBucketCounts() []uint64 { + if m != nil { + return m.BucketCounts + } + return nil +} + +func (m *IntHistogramDataPoint) GetExplicitBounds() []float64 { + if m != nil { + return m.ExplicitBounds + } + return nil +} + +func (m *IntHistogramDataPoint) GetExemplars() []*IntExemplar { + if m != nil { + return m.Exemplars + } + return nil +} + +// HistogramDataPoint is a single data point in a timeseries that describes the +// time-varying values of a Histogram of double values. A Histogram contains +// summary statistics for a population of values, it may optionally contain the +// distribution of those values across a set of buckets. +type DoubleHistogramDataPoint struct { + // The set of labels that uniquely identify this timeseries. + Labels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels"` + // start_time_unix_nano is the last time when the aggregation value was reset + // to "zero". For some metric types this is ignored, see data types for more + // details. + // + // The aggregation value is over the time interval (start_time_unix_nano, + // time_unix_nano]. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + // + // Value of 0 indicates that the timestamp is unspecified. In that case the + // timestamp may be decided by the backend. + StartTimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // time_unix_nano is the moment when this aggregation value was reported. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,3,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // count is the number of values in the population. Must be non-negative. This + // value must be equal to the sum of the "count" fields in buckets if a + // histogram is provided. + Count uint64 `protobuf:"fixed64,4,opt,name=count,proto3" json:"count,omitempty"` + // sum of the values in the population. If count is zero then this field + // must be zero. This value must be equal to the sum of the "sum" fields in + // buckets if a histogram is provided. + Sum float64 `protobuf:"fixed64,5,opt,name=sum,proto3" json:"sum,omitempty"` + // bucket_counts is an optional field contains the count values of histogram + // for each bucket. + // + // The sum of the bucket_counts must equal the value in the count field. + // + // The number of elements in bucket_counts array must be by one greater than + // the number of elements in explicit_bounds array. + BucketCounts []uint64 `protobuf:"fixed64,6,rep,packed,name=bucket_counts,json=bucketCounts,proto3" json:"bucket_counts,omitempty"` + // explicit_bounds specifies buckets with explicitly defined bounds for values. + // The bucket boundaries are described by "bounds" field. + // + // This defines size(bounds) + 1 (= N) buckets. The boundaries for bucket + // at index i are: + // + // (-infinity, bounds[i]) for i == 0 + // [bounds[i-1], bounds[i]) for 0 < i < N-1 + // [bounds[i], +infinity) for i == N-1 + // The values in bounds array must be strictly increasing. + // + // Note: only [a, b) intervals are currently supported for each bucket except the first one. + // If we decide to also support (a, b] intervals we should add support for these by defining + // a boolean value which decides what type of intervals to use. + ExplicitBounds []float64 `protobuf:"fixed64,7,rep,packed,name=explicit_bounds,json=explicitBounds,proto3" json:"explicit_bounds,omitempty"` + // (Optional) List of exemplars collected from + // measurements that were used to form the data point + Exemplars []*DoubleExemplar `protobuf:"bytes,8,rep,name=exemplars,proto3" json:"exemplars,omitempty"` +} + +func (m *DoubleHistogramDataPoint) Reset() { *m = DoubleHistogramDataPoint{} } +func (m *DoubleHistogramDataPoint) String() string { return proto.CompactTextString(m) } +func (*DoubleHistogramDataPoint) ProtoMessage() {} +func (*DoubleHistogramDataPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{13} +} +func (m *DoubleHistogramDataPoint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleHistogramDataPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleHistogramDataPoint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleHistogramDataPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleHistogramDataPoint.Merge(m, src) +} +func (m *DoubleHistogramDataPoint) XXX_Size() int { + return m.Size() +} +func (m *DoubleHistogramDataPoint) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleHistogramDataPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleHistogramDataPoint proto.InternalMessageInfo + +func (m *DoubleHistogramDataPoint) GetLabels() []v11.StringKeyValue { + if m != nil { + return m.Labels + } + return nil +} + +func (m *DoubleHistogramDataPoint) GetStartTimeUnixNano() uint64 { + if m != nil { + return m.StartTimeUnixNano + } + return 0 +} + +func (m *DoubleHistogramDataPoint) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *DoubleHistogramDataPoint) GetCount() uint64 { + if m != nil { + return m.Count + } + return 0 +} + +func (m *DoubleHistogramDataPoint) GetSum() float64 { + if m != nil { + return m.Sum + } + return 0 +} + +func (m *DoubleHistogramDataPoint) GetBucketCounts() []uint64 { + if m != nil { + return m.BucketCounts + } + return nil +} + +func (m *DoubleHistogramDataPoint) GetExplicitBounds() []float64 { + if m != nil { + return m.ExplicitBounds + } + return nil +} + +func (m *DoubleHistogramDataPoint) GetExemplars() []*DoubleExemplar { + if m != nil { + return m.Exemplars + } + return nil +} + +// DoubleSummaryDataPoint is a single data point in a timeseries that describes the +// time-varying values of a Summary metric. +type DoubleSummaryDataPoint struct { + // The set of labels that uniquely identify this timeseries. + Labels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels"` + // start_time_unix_nano is the last time when the aggregation value was reset + // to "zero". For some metric types this is ignored, see data types for more + // details. + // + // The aggregation value is over the time interval (start_time_unix_nano, + // time_unix_nano]. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + // + // Value of 0 indicates that the timestamp is unspecified. In that case the + // timestamp may be decided by the backend. + StartTimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // time_unix_nano is the moment when this aggregation value was reported. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,3,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // count is the number of values in the population. Must be non-negative. + Count uint64 `protobuf:"fixed64,4,opt,name=count,proto3" json:"count,omitempty"` + // sum of the values in the population. If count is zero then this field + // must be zero. + Sum float64 `protobuf:"fixed64,5,opt,name=sum,proto3" json:"sum,omitempty"` + // (Optional) list of values at different quantiles of the distribution calculated + // from the current snapshot. The quantiles must be strictly increasing. + QuantileValues []*DoubleSummaryDataPoint_ValueAtQuantile `protobuf:"bytes,6,rep,name=quantile_values,json=quantileValues,proto3" json:"quantile_values,omitempty"` +} + +func (m *DoubleSummaryDataPoint) Reset() { *m = DoubleSummaryDataPoint{} } +func (m *DoubleSummaryDataPoint) String() string { return proto.CompactTextString(m) } +func (*DoubleSummaryDataPoint) ProtoMessage() {} +func (*DoubleSummaryDataPoint) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{14} +} +func (m *DoubleSummaryDataPoint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleSummaryDataPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleSummaryDataPoint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleSummaryDataPoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleSummaryDataPoint.Merge(m, src) +} +func (m *DoubleSummaryDataPoint) XXX_Size() int { + return m.Size() +} +func (m *DoubleSummaryDataPoint) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleSummaryDataPoint.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleSummaryDataPoint proto.InternalMessageInfo + +func (m *DoubleSummaryDataPoint) GetLabels() []v11.StringKeyValue { + if m != nil { + return m.Labels + } + return nil +} + +func (m *DoubleSummaryDataPoint) GetStartTimeUnixNano() uint64 { + if m != nil { + return m.StartTimeUnixNano + } + return 0 +} + +func (m *DoubleSummaryDataPoint) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *DoubleSummaryDataPoint) GetCount() uint64 { + if m != nil { + return m.Count + } + return 0 +} + +func (m *DoubleSummaryDataPoint) GetSum() float64 { + if m != nil { + return m.Sum + } + return 0 +} + +func (m *DoubleSummaryDataPoint) GetQuantileValues() []*DoubleSummaryDataPoint_ValueAtQuantile { + if m != nil { + return m.QuantileValues + } + return nil +} + +// Represents the value at a given quantile of a distribution. +// +// To record Min and Max values following conventions are used: +// - The 1.0 quantile is equivalent to the maximum value observed. +// - The 0.0 quantile is equivalent to the minimum value observed. +// +// See the following issue for more context: +// https://github.com/open-telemetry/opentelemetry-proto/issues/125 +type DoubleSummaryDataPoint_ValueAtQuantile struct { + // The quantile of a distribution. Must be in the interval + // [0.0, 1.0]. + Quantile float64 `protobuf:"fixed64,1,opt,name=quantile,proto3" json:"quantile,omitempty"` + // The value at the given quantile of a distribution. + Value float64 `protobuf:"fixed64,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) Reset() { + *m = DoubleSummaryDataPoint_ValueAtQuantile{} +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) String() string { return proto.CompactTextString(m) } +func (*DoubleSummaryDataPoint_ValueAtQuantile) ProtoMessage() {} +func (*DoubleSummaryDataPoint_ValueAtQuantile) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{14, 0} +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleSummaryDataPoint_ValueAtQuantile.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleSummaryDataPoint_ValueAtQuantile.Merge(m, src) +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) XXX_Size() int { + return m.Size() +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleSummaryDataPoint_ValueAtQuantile.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleSummaryDataPoint_ValueAtQuantile proto.InternalMessageInfo + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) GetQuantile() float64 { + if m != nil { + return m.Quantile + } + return 0 +} + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) GetValue() float64 { + if m != nil { + return m.Value + } + return 0 +} + +// A representation of an exemplar, which is a sample input int measurement. +// Exemplars also hold information about the environment when the measurement +// was recorded, for example the span and trace ID of the active span when the +// exemplar was recorded. +type IntExemplar struct { + // The set of labels that were filtered out by the aggregator, but recorded + // alongside the original measurement. Only labels that were filtered out + // by the aggregator should be included + FilteredLabels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=filtered_labels,json=filteredLabels,proto3" json:"filtered_labels"` + // time_unix_nano is the exact time when this exemplar was recorded + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // Numerical int value of the measurement that was recorded. + Value int64 `protobuf:"fixed64,3,opt,name=value,proto3" json:"value,omitempty"` + // (Optional) Span ID of the exemplar trace. + // span_id may be missing if the measurement is not recorded inside a trace + // or if the trace is not sampled. + SpanId go_opentelemetry_io_collector_internal_data.SpanID `protobuf:"bytes,4,opt,name=span_id,json=spanId,proto3,customtype=go.opentelemetry.io/collector/internal/data.SpanID" json:"span_id"` + // (Optional) Trace ID of the exemplar trace. + // trace_id may be missing if the measurement is not recorded inside a trace + // or if the trace is not sampled. + TraceId go_opentelemetry_io_collector_internal_data.TraceID `protobuf:"bytes,5,opt,name=trace_id,json=traceId,proto3,customtype=go.opentelemetry.io/collector/internal/data.TraceID" json:"trace_id"` +} + +func (m *IntExemplar) Reset() { *m = IntExemplar{} } +func (m *IntExemplar) String() string { return proto.CompactTextString(m) } +func (*IntExemplar) ProtoMessage() {} +func (*IntExemplar) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{15} +} +func (m *IntExemplar) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntExemplar) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntExemplar.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntExemplar) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntExemplar.Merge(m, src) +} +func (m *IntExemplar) XXX_Size() int { + return m.Size() +} +func (m *IntExemplar) XXX_DiscardUnknown() { + xxx_messageInfo_IntExemplar.DiscardUnknown(m) +} + +var xxx_messageInfo_IntExemplar proto.InternalMessageInfo + +func (m *IntExemplar) GetFilteredLabels() []v11.StringKeyValue { + if m != nil { + return m.FilteredLabels + } + return nil +} + +func (m *IntExemplar) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *IntExemplar) GetValue() int64 { + if m != nil { + return m.Value + } + return 0 +} + +// A representation of an exemplar, which is a sample input double measurement. +// Exemplars also hold information about the environment when the measurement +// was recorded, for example the span and trace ID of the active span when the +// exemplar was recorded. +type DoubleExemplar struct { + // The set of labels that were filtered out by the aggregator, but recorded + // alongside the original measurement. Only labels that were filtered out + // by the aggregator should be included + FilteredLabels []v11.StringKeyValue `protobuf:"bytes,1,rep,name=filtered_labels,json=filteredLabels,proto3" json:"filtered_labels"` + // time_unix_nano is the exact time when this exemplar was recorded + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January + // 1970. + TimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // Numerical double value of the measurement that was recorded. + Value float64 `protobuf:"fixed64,3,opt,name=value,proto3" json:"value,omitempty"` + // (Optional) Span ID of the exemplar trace. + // span_id may be missing if the measurement is not recorded inside a trace + // or if the trace is not sampled. + SpanId go_opentelemetry_io_collector_internal_data.SpanID `protobuf:"bytes,4,opt,name=span_id,json=spanId,proto3,customtype=go.opentelemetry.io/collector/internal/data.SpanID" json:"span_id"` + // (Optional) Trace ID of the exemplar trace. + // trace_id may be missing if the measurement is not recorded inside a trace + // or if the trace is not sampled. + TraceId go_opentelemetry_io_collector_internal_data.TraceID `protobuf:"bytes,5,opt,name=trace_id,json=traceId,proto3,customtype=go.opentelemetry.io/collector/internal/data.TraceID" json:"trace_id"` +} + +func (m *DoubleExemplar) Reset() { *m = DoubleExemplar{} } +func (m *DoubleExemplar) String() string { return proto.CompactTextString(m) } +func (*DoubleExemplar) ProtoMessage() {} +func (*DoubleExemplar) Descriptor() ([]byte, []int) { + return fileDescriptor_3c3112f9fa006917, []int{16} +} +func (m *DoubleExemplar) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DoubleExemplar) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DoubleExemplar.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DoubleExemplar) XXX_Merge(src proto.Message) { + xxx_messageInfo_DoubleExemplar.Merge(m, src) +} +func (m *DoubleExemplar) XXX_Size() int { + return m.Size() +} +func (m *DoubleExemplar) XXX_DiscardUnknown() { + xxx_messageInfo_DoubleExemplar.DiscardUnknown(m) +} + +var xxx_messageInfo_DoubleExemplar proto.InternalMessageInfo + +func (m *DoubleExemplar) GetFilteredLabels() []v11.StringKeyValue { + if m != nil { + return m.FilteredLabels + } + return nil +} + +func (m *DoubleExemplar) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *DoubleExemplar) GetValue() float64 { + if m != nil { + return m.Value + } + return 0 +} + +func init() { + proto.RegisterEnum("opentelemetry.proto.metrics.v1.AggregationTemporality", AggregationTemporality_name, AggregationTemporality_value) + proto.RegisterType((*ResourceMetrics)(nil), "opentelemetry.proto.metrics.v1.ResourceMetrics") + proto.RegisterType((*InstrumentationLibraryMetrics)(nil), "opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics") + proto.RegisterType((*Metric)(nil), "opentelemetry.proto.metrics.v1.Metric") + proto.RegisterType((*IntGauge)(nil), "opentelemetry.proto.metrics.v1.IntGauge") + proto.RegisterType((*DoubleGauge)(nil), "opentelemetry.proto.metrics.v1.DoubleGauge") + proto.RegisterType((*IntSum)(nil), "opentelemetry.proto.metrics.v1.IntSum") + proto.RegisterType((*DoubleSum)(nil), "opentelemetry.proto.metrics.v1.DoubleSum") + proto.RegisterType((*IntHistogram)(nil), "opentelemetry.proto.metrics.v1.IntHistogram") + proto.RegisterType((*DoubleHistogram)(nil), "opentelemetry.proto.metrics.v1.DoubleHistogram") + proto.RegisterType((*DoubleSummary)(nil), "opentelemetry.proto.metrics.v1.DoubleSummary") + proto.RegisterType((*IntDataPoint)(nil), "opentelemetry.proto.metrics.v1.IntDataPoint") + proto.RegisterType((*DoubleDataPoint)(nil), "opentelemetry.proto.metrics.v1.DoubleDataPoint") + proto.RegisterType((*IntHistogramDataPoint)(nil), "opentelemetry.proto.metrics.v1.IntHistogramDataPoint") + proto.RegisterType((*DoubleHistogramDataPoint)(nil), "opentelemetry.proto.metrics.v1.DoubleHistogramDataPoint") + proto.RegisterType((*DoubleSummaryDataPoint)(nil), "opentelemetry.proto.metrics.v1.DoubleSummaryDataPoint") + proto.RegisterType((*DoubleSummaryDataPoint_ValueAtQuantile)(nil), "opentelemetry.proto.metrics.v1.DoubleSummaryDataPoint.ValueAtQuantile") + proto.RegisterType((*IntExemplar)(nil), "opentelemetry.proto.metrics.v1.IntExemplar") + proto.RegisterType((*DoubleExemplar)(nil), "opentelemetry.proto.metrics.v1.DoubleExemplar") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/metrics/v1/metrics.proto", fileDescriptor_3c3112f9fa006917) +} + +var fileDescriptor_3c3112f9fa006917 = []byte{ + // 1259 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xc1, 0x6b, 0x1b, 0x47, + 0x17, 0xd7, 0x4a, 0xb6, 0x2c, 0x3f, 0xc9, 0x92, 0xbf, 0x21, 0x9f, 0xb3, 0x18, 0xa2, 0x38, 0x4a, + 0x49, 0xdc, 0x24, 0x96, 0x88, 0x43, 0x42, 0x69, 0x29, 0x54, 0xb2, 0x14, 0x5b, 0x8d, 0xec, 0xa8, + 0x2b, 0xd9, 0x25, 0x25, 0xb0, 0xac, 0xb4, 0x53, 0x65, 0xe8, 0xee, 0x8c, 0xba, 0x3b, 0x6b, 0xec, + 0x6b, 0xa1, 0xb7, 0x42, 0x0b, 0x3d, 0xb5, 0xfd, 0x87, 0x72, 0xcc, 0xa1, 0x90, 0x52, 0x68, 0x28, + 0x09, 0xf4, 0xd2, 0x53, 0xef, 0x3d, 0x94, 0x99, 0xdd, 0xb5, 0x24, 0x7b, 0x6d, 0xc9, 0x4d, 0x0a, + 0x76, 0x6e, 0x6f, 0xde, 0xbc, 0xf7, 0x9b, 0xf7, 0x7e, 0xef, 0xcd, 0xdb, 0x91, 0xe0, 0x16, 0xeb, + 0x63, 0xca, 0xb1, 0x85, 0x6d, 0xcc, 0x9d, 0xfd, 0x52, 0xdf, 0x61, 0x9c, 0x95, 0x84, 0x4c, 0xba, + 0x6e, 0x69, 0xf7, 0x76, 0x28, 0x16, 0xe5, 0x06, 0xca, 0x8f, 0x58, 0xfb, 0xca, 0x62, 0x68, 0xb2, + 0x7b, 0x7b, 0xf1, 0x42, 0x8f, 0xf5, 0x98, 0x8f, 0x21, 0x24, 0xdf, 0x60, 0xf1, 0x46, 0xd4, 0x19, + 0x5d, 0x66, 0xdb, 0x8c, 0x8a, 0x23, 0x7c, 0x29, 0xb0, 0x2d, 0x46, 0xd9, 0x3a, 0xd8, 0x65, 0x9e, + 0xd3, 0xc5, 0xc2, 0x3a, 0x94, 0x7d, 0xfb, 0xc2, 0x1f, 0x0a, 0xe4, 0xb4, 0x40, 0xb5, 0xe9, 0x07, + 0x82, 0x1e, 0x40, 0x2a, 0xb4, 0x52, 0x95, 0x25, 0x65, 0x39, 0xbd, 0xfa, 0x6e, 0x31, 0x2a, 0xf0, + 0x03, 0xa8, 0xdd, 0xdb, 0xc5, 0x10, 0xa3, 0x32, 0xf5, 0xf4, 0xc5, 0xe5, 0x98, 0x76, 0x00, 0x80, + 0xbe, 0x56, 0xe0, 0x32, 0xa1, 0x2e, 0x77, 0x3c, 0x1b, 0x53, 0x6e, 0x70, 0xc2, 0xa8, 0x6e, 0x91, + 0x8e, 0x63, 0x38, 0xfb, 0x7a, 0x90, 0xb9, 0x1a, 0x5f, 0x4a, 0x2c, 0xa7, 0x57, 0x3f, 0x2c, 0x9e, + 0xcc, 0x4e, 0xb1, 0x3e, 0x0a, 0xd3, 0xf0, 0x51, 0x82, 0xa8, 0xb5, 0x4b, 0xe4, 0xa4, 0xed, 0xc2, + 0x73, 0x05, 0x2e, 0x9d, 0x08, 0x80, 0x38, 0x5c, 0x3c, 0x26, 0xd0, 0x80, 0x85, 0xbb, 0x91, 0x01, + 0x06, 0xf4, 0x1f, 0x1b, 0x5f, 0xc0, 0xc8, 0x42, 0x74, 0x78, 0xe8, 0x23, 0x98, 0x19, 0xa5, 0xe1, + 0xda, 0x38, 0x1a, 0xfc, 0x78, 0xb5, 0xd0, 0xad, 0xf0, 0xed, 0x34, 0x24, 0x7d, 0x1d, 0x42, 0x30, + 0x45, 0x0d, 0xdb, 0xaf, 0xda, 0xac, 0x26, 0x65, 0xb4, 0x04, 0x69, 0x13, 0xbb, 0x5d, 0x87, 0xf4, + 0xc5, 0xb1, 0x6a, 0x5c, 0x6e, 0x0d, 0xab, 0x84, 0x97, 0x47, 0x09, 0x57, 0x13, 0xbe, 0x97, 0x90, + 0xd1, 0x3a, 0xcc, 0x12, 0xca, 0xf5, 0x9e, 0xe1, 0xf5, 0xb0, 0x3a, 0x25, 0xd3, 0x5f, 0x1e, 0x5f, + 0x1f, 0xbe, 0x2e, 0xec, 0x37, 0x62, 0x5a, 0x8a, 0x04, 0x32, 0x6a, 0x42, 0xc6, 0x64, 0x5e, 0xc7, + 0xc2, 0x01, 0xd6, 0xb4, 0xc4, 0xba, 0x39, 0x0e, 0xab, 0x2a, 0x7d, 0x42, 0xb8, 0xb4, 0x39, 0x58, + 0xa2, 0x32, 0xcc, 0x88, 0xd0, 0x5c, 0xcf, 0x56, 0x93, 0x12, 0xec, 0xda, 0x04, 0x81, 0xb5, 0x3c, + 0x7b, 0x23, 0xa6, 0x25, 0x89, 0x94, 0xd0, 0xc7, 0x00, 0x41, 0x50, 0x02, 0x65, 0xe6, 0x84, 0x1e, + 0x3f, 0x12, 0x92, 0x0f, 0x34, 0x6b, 0x86, 0x0b, 0xd4, 0x82, 0x39, 0x11, 0xce, 0x13, 0xe2, 0x72, + 0xd6, 0x73, 0x0c, 0x5b, 0x4d, 0x49, 0xb8, 0x5b, 0x13, 0x04, 0xb5, 0x11, 0xfa, 0x6c, 0xc4, 0xb4, + 0x0c, 0x19, 0x5a, 0xa3, 0xc7, 0x30, 0x1f, 0x04, 0x38, 0xc0, 0x9d, 0x95, 0xb8, 0xa5, 0xc9, 0xc2, + 0x1c, 0x86, 0xce, 0x99, 0xa3, 0x2a, 0xb4, 0x03, 0xd9, 0x41, 0xfa, 0xb6, 0x68, 0xf0, 0xb4, 0xc4, + 0x5e, 0x99, 0x98, 0x02, 0xe1, 0xb4, 0x11, 0xd3, 0xe6, 0xcc, 0x61, 0x45, 0x25, 0x09, 0x53, 0xa6, + 0xc1, 0x8d, 0xc2, 0x23, 0x48, 0x85, 0xbd, 0x80, 0x36, 0x21, 0x2d, 0x74, 0x7a, 0x9f, 0x11, 0xca, + 0x5d, 0x55, 0x91, 0x3d, 0x3e, 0x09, 0x39, 0x55, 0x83, 0x1b, 0x4d, 0xe1, 0xa4, 0x81, 0x19, 0x8a, + 0x6e, 0x41, 0x87, 0xf4, 0x50, 0x6b, 0xa0, 0x66, 0x14, 0xfa, 0x84, 0x14, 0x45, 0x1f, 0xf0, 0xa7, + 0x02, 0x49, 0xbf, 0x5f, 0xde, 0x70, 0xe8, 0x88, 0xc1, 0x45, 0xa3, 0xd7, 0x73, 0x70, 0xcf, 0x9f, + 0x2d, 0x1c, 0xdb, 0x7d, 0xe6, 0x18, 0x16, 0xe1, 0xfb, 0xf2, 0x52, 0x66, 0x57, 0xef, 0x8d, 0x83, + 0x2e, 0x0f, 0xdc, 0xdb, 0x03, 0x6f, 0x6d, 0xc1, 0x88, 0xd4, 0xa3, 0x2b, 0x90, 0x21, 0xae, 0x6e, + 0x33, 0xca, 0x38, 0xa3, 0xa4, 0x2b, 0xef, 0x77, 0x4a, 0x4b, 0x13, 0x77, 0x33, 0x54, 0x15, 0xfe, + 0x52, 0x60, 0xf6, 0xa0, 0xa8, 0x6f, 0x9e, 0xcd, 0x33, 0x99, 0xf3, 0x73, 0x05, 0x32, 0xc3, 0x97, + 0x0f, 0xed, 0x44, 0xa5, 0x7d, 0xf7, 0x34, 0xf7, 0xf7, 0x6c, 0x24, 0x5f, 0xf8, 0x4d, 0x81, 0xdc, + 0xa1, 0xeb, 0x8f, 0x1e, 0x45, 0x25, 0xf7, 0xde, 0x29, 0x87, 0xc8, 0x19, 0xc9, 0xef, 0x09, 0xcc, + 0x8d, 0x4c, 0x20, 0xf4, 0x69, 0x54, 0x72, 0xf7, 0x4e, 0x35, 0xc5, 0xa2, 0xa7, 0xc0, 0xf7, 0x71, + 0xd9, 0x23, 0x07, 0x9b, 0xe8, 0x01, 0x24, 0x2d, 0xa3, 0x83, 0xad, 0xf0, 0x90, 0x95, 0x31, 0x6f, + 0x81, 0x16, 0x77, 0x08, 0xed, 0x3d, 0xc0, 0xfb, 0x3b, 0x86, 0xe5, 0x85, 0xaf, 0xa2, 0x00, 0x02, + 0x95, 0xe0, 0x82, 0xcb, 0x0d, 0x87, 0xeb, 0x9c, 0xd8, 0x58, 0xf7, 0x28, 0xd9, 0xd3, 0xa9, 0x41, + 0x99, 0x64, 0x2d, 0xa9, 0xfd, 0x4f, 0xee, 0xb5, 0x89, 0x8d, 0xb7, 0x29, 0xd9, 0xdb, 0x32, 0x28, + 0x43, 0xef, 0x40, 0xf6, 0x90, 0x69, 0x42, 0x9a, 0x66, 0xf8, 0xb0, 0xd5, 0x05, 0x98, 0xde, 0x15, + 0xa7, 0xc9, 0xef, 0xf5, 0xbc, 0xe6, 0x2f, 0x50, 0x1d, 0x66, 0xf1, 0x1e, 0xb6, 0xfb, 0x96, 0xe1, + 0xb8, 0xea, 0xb4, 0x0c, 0xfe, 0xe6, 0x04, 0xbd, 0x5d, 0x0b, 0x7c, 0xb4, 0x81, 0x77, 0xe1, 0x87, + 0x78, 0xd8, 0x5f, 0xe7, 0x98, 0x18, 0x25, 0x24, 0xa6, 0x71, 0x94, 0x98, 0xe2, 0x64, 0xad, 0x13, + 0xc5, 0xcd, 0xdf, 0x71, 0xf8, 0x7f, 0xe4, 0x48, 0x38, 0x2f, 0x0c, 0x75, 0x99, 0x47, 0xb9, 0x64, + 0x28, 0xa9, 0xf9, 0x0b, 0x34, 0x0f, 0x09, 0xf1, 0x3e, 0x9a, 0x96, 0xed, 0x24, 0x44, 0x74, 0x15, + 0xe6, 0x3a, 0x5e, 0xf7, 0x0b, 0xcc, 0x75, 0x69, 0xe1, 0xaa, 0xc9, 0xa5, 0x84, 0x00, 0xf3, 0x95, + 0x6b, 0x52, 0x87, 0xae, 0x43, 0x0e, 0xef, 0xf5, 0x2d, 0xd2, 0x25, 0x5c, 0xef, 0x30, 0x8f, 0x9a, + 0xae, 0x3a, 0xb3, 0x94, 0x58, 0x56, 0xb4, 0x6c, 0xa8, 0xae, 0x48, 0xed, 0x68, 0x6b, 0xa6, 0x5e, + 0xab, 0x35, 0xbf, 0x4a, 0x80, 0x7a, 0xdc, 0xd0, 0x7a, 0x3b, 0x2a, 0xa0, 0xfc, 0x17, 0x15, 0x68, + 0x1c, 0xad, 0xc0, 0x6b, 0xdc, 0x81, 0x1f, 0x13, 0xb0, 0x10, 0x3d, 0x5c, 0xdf, 0xaa, 0x12, 0x30, + 0xc8, 0x7d, 0xe9, 0x19, 0x94, 0x13, 0x0b, 0xeb, 0x72, 0x94, 0xf8, 0x45, 0x48, 0xaf, 0xde, 0xff, + 0x77, 0x5f, 0x9e, 0xa2, 0xcc, 0xb1, 0xcc, 0x3f, 0x09, 0x40, 0xb5, 0x6c, 0x08, 0x2f, 0x37, 0xdc, + 0xc5, 0x35, 0xc8, 0x1d, 0x32, 0x41, 0x8b, 0x90, 0x0a, 0x8d, 0xe4, 0xaf, 0x3d, 0x45, 0x3b, 0x58, + 0x0f, 0xc6, 0x5d, 0x7c, 0x68, 0xdc, 0x15, 0x7e, 0x8e, 0x43, 0x7a, 0xe8, 0xf2, 0xa0, 0xc7, 0x90, + 0xfb, 0x9c, 0x58, 0x1c, 0x3b, 0xd8, 0xd4, 0x5f, 0xbf, 0x34, 0xd9, 0x10, 0xab, 0xe1, 0x97, 0xe8, + 0x28, 0xe3, 0xf1, 0x93, 0x06, 0x73, 0x62, 0xf8, 0x8b, 0xd5, 0x82, 0x19, 0xb7, 0x6f, 0x50, 0x9d, + 0x98, 0xb2, 0x12, 0x99, 0xca, 0xfb, 0xe2, 0x88, 0x5f, 0x5f, 0x5c, 0x5e, 0xed, 0xb1, 0x43, 0xb1, + 0x11, 0x56, 0xea, 0x32, 0xcb, 0xc2, 0x5d, 0xce, 0x9c, 0x12, 0xa1, 0x1c, 0x3b, 0xd4, 0xb0, 0x4a, + 0xe2, 0x43, 0x5e, 0x6c, 0xf5, 0x0d, 0x5a, 0xaf, 0x6a, 0x49, 0x01, 0x55, 0x37, 0xd1, 0x0e, 0xa4, + 0xb8, 0x63, 0x74, 0xb1, 0x40, 0x9d, 0x96, 0xa8, 0x1f, 0x04, 0xa8, 0x77, 0x4e, 0x83, 0xda, 0x16, + 0x18, 0xf5, 0xaa, 0x36, 0x23, 0xc1, 0xea, 0x66, 0xe1, 0x79, 0x1c, 0xb2, 0xa3, 0x37, 0xe2, 0xec, + 0x31, 0xab, 0x9c, 0x47, 0x66, 0x6f, 0x7c, 0xa3, 0xc0, 0x42, 0xf4, 0x03, 0x11, 0x5d, 0x87, 0xab, + 0xe5, 0xf5, 0x75, 0xad, 0xb6, 0x5e, 0x6e, 0xd7, 0x1f, 0x6e, 0xe9, 0xed, 0xda, 0x66, 0xf3, 0xa1, + 0x56, 0x6e, 0xd4, 0xdb, 0x8f, 0xf4, 0xed, 0xad, 0x56, 0xb3, 0xb6, 0x56, 0xbf, 0x5f, 0xaf, 0x55, + 0xe7, 0x63, 0xe8, 0x0a, 0x5c, 0x3a, 0xce, 0xb0, 0x5a, 0x6b, 0xb4, 0xcb, 0xf3, 0x0a, 0xba, 0x06, + 0x85, 0xe3, 0x4c, 0xd6, 0xb6, 0x37, 0xb7, 0x1b, 0xe5, 0x76, 0x7d, 0xa7, 0x36, 0x1f, 0xaf, 0xfc, + 0xa4, 0x3c, 0x7d, 0x99, 0x57, 0x9e, 0xbd, 0xcc, 0x2b, 0xbf, 0xbf, 0xcc, 0x2b, 0xdf, 0xbd, 0xca, + 0xc7, 0x9e, 0xbd, 0xca, 0xc7, 0x7e, 0x79, 0x95, 0x8f, 0xc1, 0x15, 0xc2, 0xc6, 0xdc, 0xfc, 0x4a, + 0x26, 0xf8, 0x97, 0xa9, 0x29, 0x36, 0x9a, 0xca, 0x67, 0x5b, 0xa7, 0xa0, 0xa6, 0x34, 0x62, 0xb8, + 0x22, 0xb1, 0x57, 0x7a, 0x98, 0x0e, 0xfd, 0xcf, 0xd8, 0x49, 0x4a, 0xed, 0x9d, 0x7f, 0x02, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x76, 0x79, 0x73, 0x90, 0x14, 0x00, 0x00, +} + +func (m *ResourceMetrics) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourceMetrics) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceMetrics) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.InstrumentationLibraryMetrics) > 0 { + for iNdEx := len(m.InstrumentationLibraryMetrics) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.InstrumentationLibraryMetrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *InstrumentationLibraryMetrics) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InstrumentationLibraryMetrics) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InstrumentationLibraryMetrics) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Metrics) > 0 { + for iNdEx := len(m.Metrics) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Metrics[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.InstrumentationLibrary.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Metric) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Metric) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Data != nil { + { + size := m.Data.Size() + i -= size + if _, err := m.Data.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + if len(m.Unit) > 0 { + i -= len(m.Unit) + copy(dAtA[i:], m.Unit) + i = encodeVarintMetrics(dAtA, i, uint64(len(m.Unit))) + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintMetrics(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintMetrics(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Metric_IntGauge) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_IntGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.IntGauge != nil { + { + size, err := m.IntGauge.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + return len(dAtA) - i, nil +} +func (m *Metric_DoubleGauge) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_DoubleGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.DoubleGauge != nil { + { + size, err := m.DoubleGauge.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + return len(dAtA) - i, nil +} +func (m *Metric_IntSum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_IntSum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.IntSum != nil { + { + size, err := m.IntSum.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + return len(dAtA) - i, nil +} +func (m *Metric_DoubleSum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_DoubleSum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.DoubleSum != nil { + { + size, err := m.DoubleSum.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + return len(dAtA) - i, nil +} +func (m *Metric_IntHistogram) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_IntHistogram) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.IntHistogram != nil { + { + size, err := m.IntHistogram.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + return len(dAtA) - i, nil +} +func (m *Metric_DoubleHistogram) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_DoubleHistogram) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.DoubleHistogram != nil { + { + size, err := m.DoubleHistogram.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + return len(dAtA) - i, nil +} +func (m *Metric_DoubleSummary) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metric_DoubleSummary) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.DoubleSummary != nil { + { + size, err := m.DoubleSummary.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } + return len(dAtA) - i, nil +} +func (m *IntGauge) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntGauge) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleGauge) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleGauge) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *IntSum) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntSum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntSum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IsMonotonic { + i-- + if m.IsMonotonic { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.AggregationTemporality != 0 { + i = encodeVarintMetrics(dAtA, i, uint64(m.AggregationTemporality)) + i-- + dAtA[i] = 0x10 + } + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleSum) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleSum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleSum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IsMonotonic { + i-- + if m.IsMonotonic { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.AggregationTemporality != 0 { + i = encodeVarintMetrics(dAtA, i, uint64(m.AggregationTemporality)) + i-- + dAtA[i] = 0x10 + } + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *IntHistogram) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntHistogram) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntHistogram) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AggregationTemporality != 0 { + i = encodeVarintMetrics(dAtA, i, uint64(m.AggregationTemporality)) + i-- + dAtA[i] = 0x10 + } + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleHistogram) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleHistogram) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleHistogram) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AggregationTemporality != 0 { + i = encodeVarintMetrics(dAtA, i, uint64(m.AggregationTemporality)) + i-- + dAtA[i] = 0x10 + } + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleSummary) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleSummary) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleSummary) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DataPoints) > 0 { + for iNdEx := len(m.DataPoints) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DataPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *IntDataPoint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntDataPoint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntDataPoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Exemplars) > 0 { + for iNdEx := len(m.Exemplars) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Exemplars[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if m.Value != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Value)) + i-- + dAtA[i] = 0x21 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x19 + } + if m.StartTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.StartTimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.Labels) > 0 { + for iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Labels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleDataPoint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleDataPoint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleDataPoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Exemplars) > 0 { + for iNdEx := len(m.Exemplars) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Exemplars[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if m.Value != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value)))) + i-- + dAtA[i] = 0x21 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x19 + } + if m.StartTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.StartTimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.Labels) > 0 { + for iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Labels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *IntHistogramDataPoint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntHistogramDataPoint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntHistogramDataPoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Exemplars) > 0 { + for iNdEx := len(m.Exemplars) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Exemplars[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + if len(m.ExplicitBounds) > 0 { + for iNdEx := len(m.ExplicitBounds) - 1; iNdEx >= 0; iNdEx-- { + f10 := math.Float64bits(float64(m.ExplicitBounds[iNdEx])) + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(f10)) + } + i = encodeVarintMetrics(dAtA, i, uint64(len(m.ExplicitBounds)*8)) + i-- + dAtA[i] = 0x3a + } + if len(m.BucketCounts) > 0 { + for iNdEx := len(m.BucketCounts) - 1; iNdEx >= 0; iNdEx-- { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.BucketCounts[iNdEx])) + } + i = encodeVarintMetrics(dAtA, i, uint64(len(m.BucketCounts)*8)) + i-- + dAtA[i] = 0x32 + } + if m.Sum != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Sum)) + i-- + dAtA[i] = 0x29 + } + if m.Count != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Count)) + i-- + dAtA[i] = 0x21 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x19 + } + if m.StartTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.StartTimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.Labels) > 0 { + for iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Labels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleHistogramDataPoint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleHistogramDataPoint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleHistogramDataPoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Exemplars) > 0 { + for iNdEx := len(m.Exemplars) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Exemplars[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + if len(m.ExplicitBounds) > 0 { + for iNdEx := len(m.ExplicitBounds) - 1; iNdEx >= 0; iNdEx-- { + f11 := math.Float64bits(float64(m.ExplicitBounds[iNdEx])) + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(f11)) + } + i = encodeVarintMetrics(dAtA, i, uint64(len(m.ExplicitBounds)*8)) + i-- + dAtA[i] = 0x3a + } + if len(m.BucketCounts) > 0 { + for iNdEx := len(m.BucketCounts) - 1; iNdEx >= 0; iNdEx-- { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.BucketCounts[iNdEx])) + } + i = encodeVarintMetrics(dAtA, i, uint64(len(m.BucketCounts)*8)) + i-- + dAtA[i] = 0x32 + } + if m.Sum != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Sum)))) + i-- + dAtA[i] = 0x29 + } + if m.Count != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Count)) + i-- + dAtA[i] = 0x21 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x19 + } + if m.StartTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.StartTimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.Labels) > 0 { + for iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Labels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleSummaryDataPoint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleSummaryDataPoint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleSummaryDataPoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.QuantileValues) > 0 { + for iNdEx := len(m.QuantileValues) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.QuantileValues[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if m.Sum != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Sum)))) + i-- + dAtA[i] = 0x29 + } + if m.Count != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Count)) + i-- + dAtA[i] = 0x21 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x19 + } + if m.StartTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.StartTimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.Labels) > 0 { + for iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Labels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Value != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value)))) + i-- + dAtA[i] = 0x11 + } + if m.Quantile != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Quantile)))) + i-- + dAtA[i] = 0x9 + } + return len(dAtA) - i, nil +} + +func (m *IntExemplar) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntExemplar) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntExemplar) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.TraceId.Size() + i -= size + if _, err := m.TraceId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size := m.SpanId.Size() + i -= size + if _, err := m.SpanId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if m.Value != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Value)) + i-- + dAtA[i] = 0x19 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.FilteredLabels) > 0 { + for iNdEx := len(m.FilteredLabels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FilteredLabels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DoubleExemplar) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DoubleExemplar) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleExemplar) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.TraceId.Size() + i -= size + if _, err := m.TraceId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size := m.SpanId.Size() + i -= size + if _, err := m.SpanId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if m.Value != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value)))) + i-- + dAtA[i] = 0x19 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x11 + } + if len(m.FilteredLabels) > 0 { + for iNdEx := len(m.FilteredLabels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FilteredLabels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintMetrics(dAtA []byte, offset int, v uint64) int { + offset -= sovMetrics(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ResourceMetrics) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Resource.Size() + n += 1 + l + sovMetrics(uint64(l)) + if len(m.InstrumentationLibraryMetrics) > 0 { + for _, e := range m.InstrumentationLibraryMetrics { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *InstrumentationLibraryMetrics) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.InstrumentationLibrary.Size() + n += 1 + l + sovMetrics(uint64(l)) + if len(m.Metrics) > 0 { + for _, e := range m.Metrics { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *Metric) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + if l > 0 { + n += 1 + l + sovMetrics(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovMetrics(uint64(l)) + } + l = len(m.Unit) + if l > 0 { + n += 1 + l + sovMetrics(uint64(l)) + } + if m.Data != nil { + n += m.Data.Size() + } + return n +} + +func (m *Metric_IntGauge) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.IntGauge != nil { + l = m.IntGauge.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *Metric_DoubleGauge) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DoubleGauge != nil { + l = m.DoubleGauge.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *Metric_IntSum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.IntSum != nil { + l = m.IntSum.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *Metric_DoubleSum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DoubleSum != nil { + l = m.DoubleSum.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *Metric_IntHistogram) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.IntHistogram != nil { + l = m.IntHistogram.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *Metric_DoubleHistogram) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DoubleHistogram != nil { + l = m.DoubleHistogram.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *Metric_DoubleSummary) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DoubleSummary != nil { + l = m.DoubleSummary.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + return n +} +func (m *IntGauge) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *DoubleGauge) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *IntSum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.AggregationTemporality != 0 { + n += 1 + sovMetrics(uint64(m.AggregationTemporality)) + } + if m.IsMonotonic { + n += 2 + } + return n +} + +func (m *DoubleSum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.AggregationTemporality != 0 { + n += 1 + sovMetrics(uint64(m.AggregationTemporality)) + } + if m.IsMonotonic { + n += 2 + } + return n +} + +func (m *IntHistogram) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.AggregationTemporality != 0 { + n += 1 + sovMetrics(uint64(m.AggregationTemporality)) + } + return n +} + +func (m *DoubleHistogram) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.AggregationTemporality != 0 { + n += 1 + sovMetrics(uint64(m.AggregationTemporality)) + } + return n +} + +func (m *DoubleSummary) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DataPoints) > 0 { + for _, e := range m.DataPoints { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *IntDataPoint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.StartTimeUnixNano != 0 { + n += 9 + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Value != 0 { + n += 9 + } + if len(m.Exemplars) > 0 { + for _, e := range m.Exemplars { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *DoubleDataPoint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.StartTimeUnixNano != 0 { + n += 9 + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Value != 0 { + n += 9 + } + if len(m.Exemplars) > 0 { + for _, e := range m.Exemplars { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *IntHistogramDataPoint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.StartTimeUnixNano != 0 { + n += 9 + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Count != 0 { + n += 9 + } + if m.Sum != 0 { + n += 9 + } + if len(m.BucketCounts) > 0 { + n += 1 + sovMetrics(uint64(len(m.BucketCounts)*8)) + len(m.BucketCounts)*8 + } + if len(m.ExplicitBounds) > 0 { + n += 1 + sovMetrics(uint64(len(m.ExplicitBounds)*8)) + len(m.ExplicitBounds)*8 + } + if len(m.Exemplars) > 0 { + for _, e := range m.Exemplars { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *DoubleHistogramDataPoint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.StartTimeUnixNano != 0 { + n += 9 + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Count != 0 { + n += 9 + } + if m.Sum != 0 { + n += 9 + } + if len(m.BucketCounts) > 0 { + n += 1 + sovMetrics(uint64(len(m.BucketCounts)*8)) + len(m.BucketCounts)*8 + } + if len(m.ExplicitBounds) > 0 { + n += 1 + sovMetrics(uint64(len(m.ExplicitBounds)*8)) + len(m.ExplicitBounds)*8 + } + if len(m.Exemplars) > 0 { + for _, e := range m.Exemplars { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *DoubleSummaryDataPoint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.StartTimeUnixNano != 0 { + n += 9 + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Count != 0 { + n += 9 + } + if m.Sum != 0 { + n += 9 + } + if len(m.QuantileValues) > 0 { + for _, e := range m.QuantileValues { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + return n +} + +func (m *DoubleSummaryDataPoint_ValueAtQuantile) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Quantile != 0 { + n += 9 + } + if m.Value != 0 { + n += 9 + } + return n +} + +func (m *IntExemplar) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.FilteredLabels) > 0 { + for _, e := range m.FilteredLabels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Value != 0 { + n += 9 + } + l = m.SpanId.Size() + n += 1 + l + sovMetrics(uint64(l)) + l = m.TraceId.Size() + n += 1 + l + sovMetrics(uint64(l)) + return n +} + +func (m *DoubleExemplar) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.FilteredLabels) > 0 { + for _, e := range m.FilteredLabels { + l = e.Size() + n += 1 + l + sovMetrics(uint64(l)) + } + } + if m.TimeUnixNano != 0 { + n += 9 + } + if m.Value != 0 { + n += 9 + } + l = m.SpanId.Size() + n += 1 + l + sovMetrics(uint64(l)) + l = m.TraceId.Size() + n += 1 + l + sovMetrics(uint64(l)) + return n +} + +func sovMetrics(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozMetrics(x uint64) (n int) { + return sovMetrics(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ResourceMetrics) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResourceMetrics: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResourceMetrics: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstrumentationLibraryMetrics", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstrumentationLibraryMetrics = append(m.InstrumentationLibraryMetrics, &InstrumentationLibraryMetrics{}) + if err := m.InstrumentationLibraryMetrics[len(m.InstrumentationLibraryMetrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InstrumentationLibraryMetrics) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InstrumentationLibraryMetrics: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InstrumentationLibraryMetrics: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstrumentationLibrary", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InstrumentationLibrary.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metrics", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metrics = append(m.Metrics, &Metric{}) + if err := m.Metrics[len(m.Metrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Metric) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metric: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metric: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Unit", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Unit = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IntGauge", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &IntGauge{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_IntGauge{v} + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DoubleGauge", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &DoubleGauge{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_DoubleGauge{v} + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IntSum", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &IntSum{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_IntSum{v} + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DoubleSum", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &DoubleSum{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_DoubleSum{v} + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IntHistogram", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &IntHistogram{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_IntHistogram{v} + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DoubleHistogram", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &DoubleHistogram{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_DoubleHistogram{v} + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DoubleSummary", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &DoubleSummary{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Metric_DoubleSummary{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntGauge) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntGauge: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntGauge: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &IntDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleGauge) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleGauge: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleGauge: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &DoubleDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntSum) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntSum: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntSum: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &IntDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType) + } + m.AggregationTemporality = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AggregationTemporality |= AggregationTemporality(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsMonotonic", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsMonotonic = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleSum) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleSum: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleSum: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &DoubleDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType) + } + m.AggregationTemporality = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AggregationTemporality |= AggregationTemporality(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsMonotonic", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsMonotonic = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntHistogram) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntHistogram: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntHistogram: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &IntHistogramDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType) + } + m.AggregationTemporality = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AggregationTemporality |= AggregationTemporality(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleHistogram) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleHistogram: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleHistogram: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &DoubleHistogramDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType) + } + m.AggregationTemporality = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AggregationTemporality |= AggregationTemporality(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleSummary) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleSummary: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleSummary: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataPoints = append(m.DataPoints, &DoubleSummaryDataPoint{}) + if err := m.DataPoints[len(m.DataPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntDataPoint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntDataPoint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntDataPoint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, v11.StringKeyValue{}) + if err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType) + } + m.StartTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.StartTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + m.Value = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Value = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Exemplars = append(m.Exemplars, &IntExemplar{}) + if err := m.Exemplars[len(m.Exemplars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleDataPoint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleDataPoint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleDataPoint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, v11.StringKeyValue{}) + if err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType) + } + m.StartTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.StartTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Value = float64(math.Float64frombits(v)) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Exemplars = append(m.Exemplars, &DoubleExemplar{}) + if err := m.Exemplars[len(m.Exemplars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntHistogramDataPoint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntHistogramDataPoint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntHistogramDataPoint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, v11.StringKeyValue{}) + if err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType) + } + m.StartTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.StartTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Count = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 5: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType) + } + m.Sum = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Sum = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 6: + if wireType == 1 { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.BucketCounts = append(m.BucketCounts, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen / 8 + if elementCount != 0 && len(m.BucketCounts) == 0 { + m.BucketCounts = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.BucketCounts = append(m.BucketCounts, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field BucketCounts", wireType) + } + case 7: + if wireType == 1 { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + v2 := float64(math.Float64frombits(v)) + m.ExplicitBounds = append(m.ExplicitBounds, v2) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen / 8 + if elementCount != 0 && len(m.ExplicitBounds) == 0 { + m.ExplicitBounds = make([]float64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + v2 := float64(math.Float64frombits(v)) + m.ExplicitBounds = append(m.ExplicitBounds, v2) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field ExplicitBounds", wireType) + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Exemplars = append(m.Exemplars, &IntExemplar{}) + if err := m.Exemplars[len(m.Exemplars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleHistogramDataPoint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleHistogramDataPoint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleHistogramDataPoint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, v11.StringKeyValue{}) + if err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType) + } + m.StartTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.StartTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Count = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 5: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Sum = float64(math.Float64frombits(v)) + case 6: + if wireType == 1 { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.BucketCounts = append(m.BucketCounts, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen / 8 + if elementCount != 0 && len(m.BucketCounts) == 0 { + m.BucketCounts = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.BucketCounts = append(m.BucketCounts, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field BucketCounts", wireType) + } + case 7: + if wireType == 1 { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + v2 := float64(math.Float64frombits(v)) + m.ExplicitBounds = append(m.ExplicitBounds, v2) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + elementCount = packedLen / 8 + if elementCount != 0 && len(m.ExplicitBounds) == 0 { + m.ExplicitBounds = make([]float64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + v2 := float64(math.Float64frombits(v)) + m.ExplicitBounds = append(m.ExplicitBounds, v2) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field ExplicitBounds", wireType) + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Exemplars = append(m.Exemplars, &DoubleExemplar{}) + if err := m.Exemplars[len(m.Exemplars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleSummaryDataPoint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleSummaryDataPoint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleSummaryDataPoint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, v11.StringKeyValue{}) + if err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType) + } + m.StartTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.StartTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Count = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 5: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Sum = float64(math.Float64frombits(v)) + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field QuantileValues", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.QuantileValues = append(m.QuantileValues, &DoubleSummaryDataPoint_ValueAtQuantile{}) + if err := m.QuantileValues[len(m.QuantileValues)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleSummaryDataPoint_ValueAtQuantile) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ValueAtQuantile: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValueAtQuantile: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Quantile", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Quantile = float64(math.Float64frombits(v)) + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Value = float64(math.Float64frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntExemplar) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntExemplar: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntExemplar: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FilteredLabels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FilteredLabels = append(m.FilteredLabels, v11.StringKeyValue{}) + if err := m.FilteredLabels[len(m.FilteredLabels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + m.Value = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Value = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SpanId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TraceId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DoubleExemplar) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DoubleExemplar: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DoubleExemplar: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FilteredLabels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FilteredLabels = append(m.FilteredLabels, v11.StringKeyValue{}) + if err := m.FilteredLabels[len(m.FilteredLabels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Value = float64(math.Float64frombits(v)) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SpanId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TraceId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetrics(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMetrics + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipMetrics(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetrics + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetrics + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetrics + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthMetrics + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupMetrics + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthMetrics + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthMetrics = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowMetrics = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupMetrics = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/resource/v1/resource.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/resource/v1/resource.pb.go new file mode 100644 index 00000000000..519b6e0a7a4 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/resource/v1/resource.pb.go @@ -0,0 +1,381 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/resource/v1/resource.proto + +package v1 + +import ( + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Resource information. +type Resource struct { + // Set of labels that describe the resource. + Attributes []v1.KeyValue `protobuf:"bytes,1,rep,name=attributes,proto3" json:"attributes"` + // dropped_attributes_count is the number of dropped attributes. If the value is 0, then + // no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,2,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` +} + +func (m *Resource) Reset() { *m = Resource{} } +func (m *Resource) String() string { return proto.CompactTextString(m) } +func (*Resource) ProtoMessage() {} +func (*Resource) Descriptor() ([]byte, []int) { + return fileDescriptor_446f73eacf88f3f5, []int{0} +} +func (m *Resource) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Resource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Resource.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Resource) XXX_Merge(src proto.Message) { + xxx_messageInfo_Resource.Merge(m, src) +} +func (m *Resource) XXX_Size() int { + return m.Size() +} +func (m *Resource) XXX_DiscardUnknown() { + xxx_messageInfo_Resource.DiscardUnknown(m) +} + +var xxx_messageInfo_Resource proto.InternalMessageInfo + +func (m *Resource) GetAttributes() []v1.KeyValue { + if m != nil { + return m.Attributes + } + return nil +} + +func (m *Resource) GetDroppedAttributesCount() uint32 { + if m != nil { + return m.DroppedAttributesCount + } + return 0 +} + +func init() { + proto.RegisterType((*Resource)(nil), "opentelemetry.proto.resource.v1.Resource") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/resource/v1/resource.proto", fileDescriptor_446f73eacf88f3f5) +} + +var fileDescriptor_446f73eacf88f3f5 = []byte{ + // 285 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xcb, 0x2f, 0x48, 0xcd, + 0x2b, 0x49, 0xcd, 0x49, 0xcd, 0x4d, 0x2d, 0x29, 0xaa, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, + 0x2f, 0x4a, 0x2d, 0xce, 0x2f, 0x2d, 0x4a, 0x4e, 0xd5, 0x2f, 0x33, 0x84, 0xb3, 0xf5, 0xc0, 0x52, + 0x42, 0xf2, 0x28, 0xea, 0x21, 0x82, 0x7a, 0x70, 0x35, 0x65, 0x86, 0x52, 0x22, 0xe9, 0xf9, 0xe9, + 0xf9, 0x10, 0x63, 0x40, 0x2c, 0x88, 0x0a, 0x29, 0x2d, 0x6c, 0xd6, 0x24, 0xe7, 0xe7, 0xe6, 0xe6, + 0xe7, 0x81, 0x2c, 0x81, 0xb0, 0x20, 0x6a, 0x95, 0x26, 0x33, 0x72, 0x71, 0x04, 0x41, 0x4d, 0x14, + 0xf2, 0xe5, 0xe2, 0x4a, 0x2c, 0x29, 0x29, 0xca, 0x4c, 0x2a, 0x2d, 0x49, 0x2d, 0x96, 0x60, 0x54, + 0x60, 0xd6, 0xe0, 0x36, 0x52, 0xd7, 0xc3, 0xe6, 0x08, 0xa8, 0x19, 0x65, 0x86, 0x7a, 0xde, 0xa9, + 0x95, 0x61, 0x89, 0x39, 0xa5, 0xa9, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x21, 0x19, 0x20, + 0x64, 0xc1, 0x25, 0x91, 0x52, 0x94, 0x5f, 0x50, 0x90, 0x9a, 0x12, 0x8f, 0x10, 0x8d, 0x4f, 0xce, + 0x2f, 0xcd, 0x2b, 0x91, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x0d, 0x12, 0x83, 0xca, 0x3b, 0xc2, 0xa5, + 0x9d, 0x41, 0xb2, 0x4e, 0xf3, 0x18, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, + 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x81, + 0x4b, 0x29, 0x33, 0x5f, 0x8f, 0x40, 0xb0, 0x38, 0xf1, 0xc2, 0x7c, 0x14, 0x00, 0x92, 0x0a, 0x60, + 0x8c, 0xf2, 0x4f, 0x47, 0xd7, 0x94, 0x09, 0x0a, 0x91, 0x9c, 0x9c, 0xd4, 0xe4, 0x92, 0xfc, 0x22, + 0xfd, 0xcc, 0xbc, 0x92, 0xd4, 0xa2, 0xbc, 0xc4, 0x1c, 0xfd, 0x94, 0xc4, 0x92, 0x44, 0x7d, 0x14, + 0x85, 0xba, 0x60, 0xd3, 0x75, 0xd3, 0x53, 0xf3, 0x90, 0x23, 0x2a, 0x89, 0x0d, 0x2c, 0x6c, 0x0c, + 0x08, 0x00, 0x00, 0xff, 0xff, 0x4a, 0x6b, 0xf7, 0x11, 0xd2, 0x01, 0x00, 0x00, +} + +func (m *Resource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Resource) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Resource) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DroppedAttributesCount != 0 { + i = encodeVarintResource(dAtA, i, uint64(m.DroppedAttributesCount)) + i-- + dAtA[i] = 0x10 + } + if len(m.Attributes) > 0 { + for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintResource(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintResource(dAtA []byte, offset int, v uint64) int { + offset -= sovResource(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Resource) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Attributes) > 0 { + for _, e := range m.Attributes { + l = e.Size() + n += 1 + l + sovResource(uint64(l)) + } + } + if m.DroppedAttributesCount != 0 { + n += 1 + sovResource(uint64(m.DroppedAttributesCount)) + } + return n +} + +func sovResource(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozResource(x uint64) (n int) { + return sovResource(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Resource) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowResource + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Resource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Resource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowResource + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthResource + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthResource + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attributes = append(m.Attributes, v1.KeyValue{}) + if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType) + } + m.DroppedAttributesCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowResource + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedAttributesCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipResource(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthResource + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthResource + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipResource(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowResource + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowResource + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowResource + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthResource + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupResource + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthResource + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthResource = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowResource = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupResource = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/opentelemetry-proto-gen/trace/v1/trace.pb.go b/internal/otel_collector/internal/data/opentelemetry-proto-gen/trace/v1/trace.pb.go new file mode 100644 index 00000000000..45eb93fda66 --- /dev/null +++ b/internal/otel_collector/internal/data/opentelemetry-proto-gen/trace/v1/trace.pb.go @@ -0,0 +1,2667 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: opentelemetry/proto/trace/v1/trace.proto + +package v1 + +import ( + encoding_binary "encoding/binary" + fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + + go_opentelemetry_io_collector_internal_data "go.opentelemetry.io/collector/internal/data" + v11 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// SpanKind is the type of span. Can be used to specify additional relationships between spans +// in addition to a parent/child relationship. +type Span_SpanKind int32 + +const ( + // Unspecified. Do NOT use as default. + // Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. + Span_SPAN_KIND_UNSPECIFIED Span_SpanKind = 0 + // Indicates that the span represents an internal operation within an application, + // as opposed to an operations happening at the boundaries. Default value. + Span_SPAN_KIND_INTERNAL Span_SpanKind = 1 + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + Span_SPAN_KIND_SERVER Span_SpanKind = 2 + // Indicates that the span describes a request to some remote service. + Span_SPAN_KIND_CLIENT Span_SpanKind = 3 + // Indicates that the span describes a producer sending a message to a broker. + // Unlike CLIENT and SERVER, there is often no direct critical path latency relationship + // between producer and consumer spans. A PRODUCER span ends when the message was accepted + // by the broker while the logical processing of the message might span a much longer time. + Span_SPAN_KIND_PRODUCER Span_SpanKind = 4 + // Indicates that the span describes consumer receiving a message from a broker. + // Like the PRODUCER kind, there is often no direct critical path latency relationship + // between producer and consumer spans. + Span_SPAN_KIND_CONSUMER Span_SpanKind = 5 +) + +var Span_SpanKind_name = map[int32]string{ + 0: "SPAN_KIND_UNSPECIFIED", + 1: "SPAN_KIND_INTERNAL", + 2: "SPAN_KIND_SERVER", + 3: "SPAN_KIND_CLIENT", + 4: "SPAN_KIND_PRODUCER", + 5: "SPAN_KIND_CONSUMER", +} + +var Span_SpanKind_value = map[string]int32{ + "SPAN_KIND_UNSPECIFIED": 0, + "SPAN_KIND_INTERNAL": 1, + "SPAN_KIND_SERVER": 2, + "SPAN_KIND_CLIENT": 3, + "SPAN_KIND_PRODUCER": 4, + "SPAN_KIND_CONSUMER": 5, +} + +func (x Span_SpanKind) String() string { + return proto.EnumName(Span_SpanKind_name, int32(x)) +} + +func (Span_SpanKind) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{2, 0} +} + +type Status_DeprecatedStatusCode int32 + +const ( + Status_DEPRECATED_STATUS_CODE_OK Status_DeprecatedStatusCode = 0 + Status_DEPRECATED_STATUS_CODE_CANCELLED Status_DeprecatedStatusCode = 1 + Status_DEPRECATED_STATUS_CODE_UNKNOWN_ERROR Status_DeprecatedStatusCode = 2 + Status_DEPRECATED_STATUS_CODE_INVALID_ARGUMENT Status_DeprecatedStatusCode = 3 + Status_DEPRECATED_STATUS_CODE_DEADLINE_EXCEEDED Status_DeprecatedStatusCode = 4 + Status_DEPRECATED_STATUS_CODE_NOT_FOUND Status_DeprecatedStatusCode = 5 + Status_DEPRECATED_STATUS_CODE_ALREADY_EXISTS Status_DeprecatedStatusCode = 6 + Status_DEPRECATED_STATUS_CODE_PERMISSION_DENIED Status_DeprecatedStatusCode = 7 + Status_DEPRECATED_STATUS_CODE_RESOURCE_EXHAUSTED Status_DeprecatedStatusCode = 8 + Status_DEPRECATED_STATUS_CODE_FAILED_PRECONDITION Status_DeprecatedStatusCode = 9 + Status_DEPRECATED_STATUS_CODE_ABORTED Status_DeprecatedStatusCode = 10 + Status_DEPRECATED_STATUS_CODE_OUT_OF_RANGE Status_DeprecatedStatusCode = 11 + Status_DEPRECATED_STATUS_CODE_UNIMPLEMENTED Status_DeprecatedStatusCode = 12 + Status_DEPRECATED_STATUS_CODE_INTERNAL_ERROR Status_DeprecatedStatusCode = 13 + Status_DEPRECATED_STATUS_CODE_UNAVAILABLE Status_DeprecatedStatusCode = 14 + Status_DEPRECATED_STATUS_CODE_DATA_LOSS Status_DeprecatedStatusCode = 15 + Status_DEPRECATED_STATUS_CODE_UNAUTHENTICATED Status_DeprecatedStatusCode = 16 +) + +var Status_DeprecatedStatusCode_name = map[int32]string{ + 0: "DEPRECATED_STATUS_CODE_OK", + 1: "DEPRECATED_STATUS_CODE_CANCELLED", + 2: "DEPRECATED_STATUS_CODE_UNKNOWN_ERROR", + 3: "DEPRECATED_STATUS_CODE_INVALID_ARGUMENT", + 4: "DEPRECATED_STATUS_CODE_DEADLINE_EXCEEDED", + 5: "DEPRECATED_STATUS_CODE_NOT_FOUND", + 6: "DEPRECATED_STATUS_CODE_ALREADY_EXISTS", + 7: "DEPRECATED_STATUS_CODE_PERMISSION_DENIED", + 8: "DEPRECATED_STATUS_CODE_RESOURCE_EXHAUSTED", + 9: "DEPRECATED_STATUS_CODE_FAILED_PRECONDITION", + 10: "DEPRECATED_STATUS_CODE_ABORTED", + 11: "DEPRECATED_STATUS_CODE_OUT_OF_RANGE", + 12: "DEPRECATED_STATUS_CODE_UNIMPLEMENTED", + 13: "DEPRECATED_STATUS_CODE_INTERNAL_ERROR", + 14: "DEPRECATED_STATUS_CODE_UNAVAILABLE", + 15: "DEPRECATED_STATUS_CODE_DATA_LOSS", + 16: "DEPRECATED_STATUS_CODE_UNAUTHENTICATED", +} + +var Status_DeprecatedStatusCode_value = map[string]int32{ + "DEPRECATED_STATUS_CODE_OK": 0, + "DEPRECATED_STATUS_CODE_CANCELLED": 1, + "DEPRECATED_STATUS_CODE_UNKNOWN_ERROR": 2, + "DEPRECATED_STATUS_CODE_INVALID_ARGUMENT": 3, + "DEPRECATED_STATUS_CODE_DEADLINE_EXCEEDED": 4, + "DEPRECATED_STATUS_CODE_NOT_FOUND": 5, + "DEPRECATED_STATUS_CODE_ALREADY_EXISTS": 6, + "DEPRECATED_STATUS_CODE_PERMISSION_DENIED": 7, + "DEPRECATED_STATUS_CODE_RESOURCE_EXHAUSTED": 8, + "DEPRECATED_STATUS_CODE_FAILED_PRECONDITION": 9, + "DEPRECATED_STATUS_CODE_ABORTED": 10, + "DEPRECATED_STATUS_CODE_OUT_OF_RANGE": 11, + "DEPRECATED_STATUS_CODE_UNIMPLEMENTED": 12, + "DEPRECATED_STATUS_CODE_INTERNAL_ERROR": 13, + "DEPRECATED_STATUS_CODE_UNAVAILABLE": 14, + "DEPRECATED_STATUS_CODE_DATA_LOSS": 15, + "DEPRECATED_STATUS_CODE_UNAUTHENTICATED": 16, +} + +func (x Status_DeprecatedStatusCode) String() string { + return proto.EnumName(Status_DeprecatedStatusCode_name, int32(x)) +} + +func (Status_DeprecatedStatusCode) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{3, 0} +} + +// For the semantics of status codes see +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#set-status +type Status_StatusCode int32 + +const ( + // The default status. + Status_STATUS_CODE_UNSET Status_StatusCode = 0 + // The Span has been validated by an Application developers or Operator to have + // completed successfully. + Status_STATUS_CODE_OK Status_StatusCode = 1 + // The Span contains an error. + Status_STATUS_CODE_ERROR Status_StatusCode = 2 +) + +var Status_StatusCode_name = map[int32]string{ + 0: "STATUS_CODE_UNSET", + 1: "STATUS_CODE_OK", + 2: "STATUS_CODE_ERROR", +} + +var Status_StatusCode_value = map[string]int32{ + "STATUS_CODE_UNSET": 0, + "STATUS_CODE_OK": 1, + "STATUS_CODE_ERROR": 2, +} + +func (x Status_StatusCode) String() string { + return proto.EnumName(Status_StatusCode_name, int32(x)) +} + +func (Status_StatusCode) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{3, 1} +} + +// A collection of InstrumentationLibrarySpans from a Resource. +type ResourceSpans struct { + // The resource for the spans in this message. + // If this field is not set then no resource info is known. + Resource v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource"` + // A list of InstrumentationLibrarySpans that originate from a resource. + InstrumentationLibrarySpans []*InstrumentationLibrarySpans `protobuf:"bytes,2,rep,name=instrumentation_library_spans,json=instrumentationLibrarySpans,proto3" json:"instrumentation_library_spans,omitempty"` +} + +func (m *ResourceSpans) Reset() { *m = ResourceSpans{} } +func (m *ResourceSpans) String() string { return proto.CompactTextString(m) } +func (*ResourceSpans) ProtoMessage() {} +func (*ResourceSpans) Descriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{0} +} +func (m *ResourceSpans) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResourceSpans) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResourceSpans.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ResourceSpans) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResourceSpans.Merge(m, src) +} +func (m *ResourceSpans) XXX_Size() int { + return m.Size() +} +func (m *ResourceSpans) XXX_DiscardUnknown() { + xxx_messageInfo_ResourceSpans.DiscardUnknown(m) +} + +var xxx_messageInfo_ResourceSpans proto.InternalMessageInfo + +func (m *ResourceSpans) GetResource() v1.Resource { + if m != nil { + return m.Resource + } + return v1.Resource{} +} + +func (m *ResourceSpans) GetInstrumentationLibrarySpans() []*InstrumentationLibrarySpans { + if m != nil { + return m.InstrumentationLibrarySpans + } + return nil +} + +// A collection of Spans produced by an InstrumentationLibrary. +type InstrumentationLibrarySpans struct { + // The instrumentation library information for the spans in this message. + // If this field is not set then no library info is known. + InstrumentationLibrary v11.InstrumentationLibrary `protobuf:"bytes,1,opt,name=instrumentation_library,json=instrumentationLibrary,proto3" json:"instrumentation_library"` + // A list of Spans that originate from an instrumentation library. + Spans []*Span `protobuf:"bytes,2,rep,name=spans,proto3" json:"spans,omitempty"` +} + +func (m *InstrumentationLibrarySpans) Reset() { *m = InstrumentationLibrarySpans{} } +func (m *InstrumentationLibrarySpans) String() string { return proto.CompactTextString(m) } +func (*InstrumentationLibrarySpans) ProtoMessage() {} +func (*InstrumentationLibrarySpans) Descriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{1} +} +func (m *InstrumentationLibrarySpans) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InstrumentationLibrarySpans) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InstrumentationLibrarySpans.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InstrumentationLibrarySpans) XXX_Merge(src proto.Message) { + xxx_messageInfo_InstrumentationLibrarySpans.Merge(m, src) +} +func (m *InstrumentationLibrarySpans) XXX_Size() int { + return m.Size() +} +func (m *InstrumentationLibrarySpans) XXX_DiscardUnknown() { + xxx_messageInfo_InstrumentationLibrarySpans.DiscardUnknown(m) +} + +var xxx_messageInfo_InstrumentationLibrarySpans proto.InternalMessageInfo + +func (m *InstrumentationLibrarySpans) GetInstrumentationLibrary() v11.InstrumentationLibrary { + if m != nil { + return m.InstrumentationLibrary + } + return v11.InstrumentationLibrary{} +} + +func (m *InstrumentationLibrarySpans) GetSpans() []*Span { + if m != nil { + return m.Spans + } + return nil +} + +// Span represents a single operation within a trace. Spans can be +// nested to form a trace tree. Spans may also be linked to other spans +// from the same or different trace and form graphs. Often, a trace +// contains a root span that describes the end-to-end latency, and one +// or more subspans for its sub-operations. A trace can also contain +// multiple root spans, or none at all. Spans do not need to be +// contiguous - there may be gaps or overlaps between spans in a trace. +// +// The next available field id is 17. +type Span struct { + // A unique identifier for a trace. All spans from the same trace share + // the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes + // is considered invalid. + // + // This field is semantically required. Receiver should generate new + // random trace_id if empty or invalid trace_id was received. + // + // This field is required. + TraceId go_opentelemetry_io_collector_internal_data.TraceID `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3,customtype=go.opentelemetry.io/collector/internal/data.TraceID" json:"trace_id"` + // A unique identifier for a span within a trace, assigned when the span + // is created. The ID is an 8-byte array. An ID with all zeroes is considered + // invalid. + // + // This field is semantically required. Receiver should generate new + // random span_id if empty or invalid span_id was received. + // + // This field is required. + SpanId go_opentelemetry_io_collector_internal_data.SpanID `protobuf:"bytes,2,opt,name=span_id,json=spanId,proto3,customtype=go.opentelemetry.io/collector/internal/data.SpanID" json:"span_id"` + // trace_state conveys information about request position in multiple distributed tracing graphs. + // It is a trace_state in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header + // See also https://github.com/w3c/distributed-tracing for more details about this field. + TraceState string `protobuf:"bytes,3,opt,name=trace_state,json=traceState,proto3" json:"trace_state,omitempty"` + // The `span_id` of this span's parent span. If this is a root span, then this + // field must be empty. The ID is an 8-byte array. + ParentSpanId go_opentelemetry_io_collector_internal_data.SpanID `protobuf:"bytes,4,opt,name=parent_span_id,json=parentSpanId,proto3,customtype=go.opentelemetry.io/collector/internal/data.SpanID" json:"parent_span_id"` + // A description of the span's operation. + // + // For example, the name can be a qualified method name or a file name + // and a line number where the operation is called. A best practice is to use + // the same display name at the same call point in an application. + // This makes it easier to correlate spans in different traces. + // + // This field is semantically required to be set to non-empty string. + // When null or empty string received - receiver may use string "name" + // as a replacement. There might be smarted algorithms implemented by + // receiver to fix the empty span name. + // + // This field is required. + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` + // Distinguishes between spans generated in a particular context. For example, + // two spans with the same name may be distinguished using `CLIENT` (caller) + // and `SERVER` (callee) to identify queueing latency associated with the span. + Kind Span_SpanKind `protobuf:"varint,6,opt,name=kind,proto3,enum=opentelemetry.proto.trace.v1.Span_SpanKind" json:"kind,omitempty"` + // start_time_unix_nano is the start time of the span. On the client side, this is the time + // kept by the local machine where the span execution starts. On the server side, this + // is the time when the server's application handler starts running. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + StartTimeUnixNano uint64 `protobuf:"fixed64,7,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // end_time_unix_nano is the end time of the span. On the client side, this is the time + // kept by the local machine where the span execution ends. On the server side, this + // is the time when the server application handler stops running. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + EndTimeUnixNano uint64 `protobuf:"fixed64,8,opt,name=end_time_unix_nano,json=endTimeUnixNano,proto3" json:"end_time_unix_nano,omitempty"` + // attributes is a collection of key/value pairs. The value can be a string, + // an integer, a double or the Boolean values `true` or `false`. Note, global attributes + // like server name can be set using the resource API. Examples of attributes: + // + // "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" + // "/http/server_latency": 300 + // "abc.com/myattribute": true + // "abc.com/score": 10.239 + Attributes []v11.KeyValue `protobuf:"bytes,9,rep,name=attributes,proto3" json:"attributes"` + // dropped_attributes_count is the number of attributes that were discarded. Attributes + // can be discarded because their keys are too long or because there are too many + // attributes. If this value is 0, then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,10,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // events is a collection of Event items. + Events []*Span_Event `protobuf:"bytes,11,rep,name=events,proto3" json:"events,omitempty"` + // dropped_events_count is the number of dropped events. If the value is 0, then no + // events were dropped. + DroppedEventsCount uint32 `protobuf:"varint,12,opt,name=dropped_events_count,json=droppedEventsCount,proto3" json:"dropped_events_count,omitempty"` + // links is a collection of Links, which are references from this span to a span + // in the same or different trace. + Links []*Span_Link `protobuf:"bytes,13,rep,name=links,proto3" json:"links,omitempty"` + // dropped_links_count is the number of dropped links after the maximum size was + // enforced. If this value is 0, then no links were dropped. + DroppedLinksCount uint32 `protobuf:"varint,14,opt,name=dropped_links_count,json=droppedLinksCount,proto3" json:"dropped_links_count,omitempty"` + // An optional final status for this span. Semantically when Status isn't set, it means + // span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0). + Status Status `protobuf:"bytes,15,opt,name=status,proto3" json:"status"` +} + +func (m *Span) Reset() { *m = Span{} } +func (m *Span) String() string { return proto.CompactTextString(m) } +func (*Span) ProtoMessage() {} +func (*Span) Descriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{2} +} +func (m *Span) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Span) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Span.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Span) XXX_Merge(src proto.Message) { + xxx_messageInfo_Span.Merge(m, src) +} +func (m *Span) XXX_Size() int { + return m.Size() +} +func (m *Span) XXX_DiscardUnknown() { + xxx_messageInfo_Span.DiscardUnknown(m) +} + +var xxx_messageInfo_Span proto.InternalMessageInfo + +func (m *Span) GetTraceState() string { + if m != nil { + return m.TraceState + } + return "" +} + +func (m *Span) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Span) GetKind() Span_SpanKind { + if m != nil { + return m.Kind + } + return Span_SPAN_KIND_UNSPECIFIED +} + +func (m *Span) GetStartTimeUnixNano() uint64 { + if m != nil { + return m.StartTimeUnixNano + } + return 0 +} + +func (m *Span) GetEndTimeUnixNano() uint64 { + if m != nil { + return m.EndTimeUnixNano + } + return 0 +} + +func (m *Span) GetAttributes() []v11.KeyValue { + if m != nil { + return m.Attributes + } + return nil +} + +func (m *Span) GetDroppedAttributesCount() uint32 { + if m != nil { + return m.DroppedAttributesCount + } + return 0 +} + +func (m *Span) GetEvents() []*Span_Event { + if m != nil { + return m.Events + } + return nil +} + +func (m *Span) GetDroppedEventsCount() uint32 { + if m != nil { + return m.DroppedEventsCount + } + return 0 +} + +func (m *Span) GetLinks() []*Span_Link { + if m != nil { + return m.Links + } + return nil +} + +func (m *Span) GetDroppedLinksCount() uint32 { + if m != nil { + return m.DroppedLinksCount + } + return 0 +} + +func (m *Span) GetStatus() Status { + if m != nil { + return m.Status + } + return Status{} +} + +// Event is a time-stamped annotation of the span, consisting of user-supplied +// text description and key-value pairs. +type Span_Event struct { + // time_unix_nano is the time the event occurred. + TimeUnixNano uint64 `protobuf:"fixed64,1,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // name of the event. + // This field is semantically required to be set to non-empty string. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // attributes is a collection of attribute key/value pairs on the event. + Attributes []v11.KeyValue `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes"` + // dropped_attributes_count is the number of dropped attributes. If the value is 0, + // then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,4,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` +} + +func (m *Span_Event) Reset() { *m = Span_Event{} } +func (m *Span_Event) String() string { return proto.CompactTextString(m) } +func (*Span_Event) ProtoMessage() {} +func (*Span_Event) Descriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{2, 0} +} +func (m *Span_Event) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Span_Event) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Span_Event.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Span_Event) XXX_Merge(src proto.Message) { + xxx_messageInfo_Span_Event.Merge(m, src) +} +func (m *Span_Event) XXX_Size() int { + return m.Size() +} +func (m *Span_Event) XXX_DiscardUnknown() { + xxx_messageInfo_Span_Event.DiscardUnknown(m) +} + +var xxx_messageInfo_Span_Event proto.InternalMessageInfo + +func (m *Span_Event) GetTimeUnixNano() uint64 { + if m != nil { + return m.TimeUnixNano + } + return 0 +} + +func (m *Span_Event) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Span_Event) GetAttributes() []v11.KeyValue { + if m != nil { + return m.Attributes + } + return nil +} + +func (m *Span_Event) GetDroppedAttributesCount() uint32 { + if m != nil { + return m.DroppedAttributesCount + } + return 0 +} + +// A pointer from the current span to another span in the same trace or in a +// different trace. For example, this can be used in batching operations, +// where a single batch handler processes multiple requests from different +// traces or when the handler receives a request from a different project. +type Span_Link struct { + // A unique identifier of a trace that this linked span is part of. The ID is a + // 16-byte array. + TraceId go_opentelemetry_io_collector_internal_data.TraceID `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3,customtype=go.opentelemetry.io/collector/internal/data.TraceID" json:"trace_id"` + // A unique identifier for the linked span. The ID is an 8-byte array. + SpanId go_opentelemetry_io_collector_internal_data.SpanID `protobuf:"bytes,2,opt,name=span_id,json=spanId,proto3,customtype=go.opentelemetry.io/collector/internal/data.SpanID" json:"span_id"` + // The trace_state associated with the link. + TraceState string `protobuf:"bytes,3,opt,name=trace_state,json=traceState,proto3" json:"trace_state,omitempty"` + // attributes is a collection of attribute key/value pairs on the link. + Attributes []v11.KeyValue `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes"` + // dropped_attributes_count is the number of dropped attributes. If the value is 0, + // then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,5,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` +} + +func (m *Span_Link) Reset() { *m = Span_Link{} } +func (m *Span_Link) String() string { return proto.CompactTextString(m) } +func (*Span_Link) ProtoMessage() {} +func (*Span_Link) Descriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{2, 1} +} +func (m *Span_Link) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Span_Link) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Span_Link.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Span_Link) XXX_Merge(src proto.Message) { + xxx_messageInfo_Span_Link.Merge(m, src) +} +func (m *Span_Link) XXX_Size() int { + return m.Size() +} +func (m *Span_Link) XXX_DiscardUnknown() { + xxx_messageInfo_Span_Link.DiscardUnknown(m) +} + +var xxx_messageInfo_Span_Link proto.InternalMessageInfo + +func (m *Span_Link) GetTraceState() string { + if m != nil { + return m.TraceState + } + return "" +} + +func (m *Span_Link) GetAttributes() []v11.KeyValue { + if m != nil { + return m.Attributes + } + return nil +} + +func (m *Span_Link) GetDroppedAttributesCount() uint32 { + if m != nil { + return m.DroppedAttributesCount + } + return 0 +} + +// The Status type defines a logical error model that is suitable for different +// programming environments, including REST APIs and RPC APIs. +type Status struct { + // The deprecated status code. This is an optional field. + // + // This field is deprecated and is replaced by the `code` field below. See backward + // compatibility notes below. According to our stability guarantees this field + // will be removed in 12 months, on Oct 22, 2021. All usage of old senders and + // receivers that do not understand the `code` field MUST be phased out by then. + DeprecatedCode Status_DeprecatedStatusCode `protobuf:"varint,1,opt,name=deprecated_code,json=deprecatedCode,proto3,enum=opentelemetry.proto.trace.v1.Status_DeprecatedStatusCode" json:"deprecated_code,omitempty"` // Deprecated: Do not use. + // A developer-facing human readable error message. + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + // The status code. + Code Status_StatusCode `protobuf:"varint,3,opt,name=code,proto3,enum=opentelemetry.proto.trace.v1.Status_StatusCode" json:"code,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { + return fileDescriptor_5c407ac9c675a601, []int{3} +} +func (m *Status) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Status) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Status.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Status) XXX_Merge(src proto.Message) { + xxx_messageInfo_Status.Merge(m, src) +} +func (m *Status) XXX_Size() int { + return m.Size() +} +func (m *Status) XXX_DiscardUnknown() { + xxx_messageInfo_Status.DiscardUnknown(m) +} + +var xxx_messageInfo_Status proto.InternalMessageInfo + +// Deprecated: Do not use. +func (m *Status) GetDeprecatedCode() Status_DeprecatedStatusCode { + if m != nil { + return m.DeprecatedCode + } + return Status_DEPRECATED_STATUS_CODE_OK +} + +func (m *Status) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func (m *Status) GetCode() Status_StatusCode { + if m != nil { + return m.Code + } + return Status_STATUS_CODE_UNSET +} + +func init() { + proto.RegisterEnum("opentelemetry.proto.trace.v1.Span_SpanKind", Span_SpanKind_name, Span_SpanKind_value) + proto.RegisterEnum("opentelemetry.proto.trace.v1.Status_DeprecatedStatusCode", Status_DeprecatedStatusCode_name, Status_DeprecatedStatusCode_value) + proto.RegisterEnum("opentelemetry.proto.trace.v1.Status_StatusCode", Status_StatusCode_name, Status_StatusCode_value) + proto.RegisterType((*ResourceSpans)(nil), "opentelemetry.proto.trace.v1.ResourceSpans") + proto.RegisterType((*InstrumentationLibrarySpans)(nil), "opentelemetry.proto.trace.v1.InstrumentationLibrarySpans") + proto.RegisterType((*Span)(nil), "opentelemetry.proto.trace.v1.Span") + proto.RegisterType((*Span_Event)(nil), "opentelemetry.proto.trace.v1.Span.Event") + proto.RegisterType((*Span_Link)(nil), "opentelemetry.proto.trace.v1.Span.Link") + proto.RegisterType((*Status)(nil), "opentelemetry.proto.trace.v1.Status") +} + +func init() { + proto.RegisterFile("opentelemetry/proto/trace/v1/trace.proto", fileDescriptor_5c407ac9c675a601) +} + +var fileDescriptor_5c407ac9c675a601 = []byte{ + // 1228 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0x41, 0x6f, 0xdb, 0x46, + 0x13, 0x15, 0x6d, 0x49, 0x76, 0xc6, 0xb6, 0xcc, 0xec, 0xe7, 0xe4, 0x63, 0x9c, 0x46, 0x16, 0x54, + 0x37, 0x51, 0x92, 0x46, 0x6a, 0x1c, 0x14, 0x48, 0x5b, 0x14, 0x2d, 0x45, 0xae, 0x13, 0xc2, 0x34, + 0x29, 0x2c, 0x29, 0x37, 0xed, 0x85, 0x65, 0xcc, 0xad, 0x41, 0x44, 0x22, 0x05, 0x6a, 0x65, 0x24, + 0x87, 0xfe, 0x87, 0x5e, 0x7a, 0xe8, 0x3f, 0x0a, 0x0a, 0x14, 0xc8, 0xb1, 0x48, 0xd1, 0xa0, 0xb0, + 0xff, 0x46, 0x0f, 0xc5, 0x2e, 0x29, 0xdb, 0x32, 0x44, 0x39, 0x41, 0x91, 0x4b, 0x2f, 0x06, 0x39, + 0xf3, 0xe6, 0xbd, 0xb7, 0x33, 0xb3, 0xb4, 0x0d, 0x8d, 0x78, 0x40, 0x23, 0x46, 0x7b, 0xb4, 0x4f, + 0x59, 0xf2, 0xa2, 0x35, 0x48, 0x62, 0x16, 0xb7, 0x58, 0xe2, 0xef, 0xd3, 0xd6, 0xe1, 0xfd, 0xf4, + 0xa1, 0x29, 0x82, 0xe8, 0x83, 0x09, 0x64, 0x1a, 0x6c, 0xa6, 0x80, 0xc3, 0xfb, 0xeb, 0x6b, 0x07, + 0xf1, 0x41, 0x9c, 0x56, 0xf3, 0xa7, 0x34, 0xbd, 0x7e, 0x67, 0x1a, 0xfb, 0x7e, 0xdc, 0xef, 0xc7, + 0x11, 0xa7, 0x4f, 0x9f, 0x32, 0x6c, 0x73, 0x1a, 0x36, 0xa1, 0xc3, 0x78, 0x94, 0xa4, 0x66, 0xc6, + 0xcf, 0x29, 0xbe, 0xfe, 0x87, 0x04, 0x2b, 0x24, 0x0b, 0x39, 0x03, 0x3f, 0x1a, 0xa2, 0x1d, 0x58, + 0x1c, 0x63, 0x14, 0xa9, 0x26, 0x35, 0x96, 0xb6, 0x6e, 0x37, 0xa7, 0x99, 0x3e, 0x21, 0x3a, 0xbc, + 0xdf, 0x1c, 0x33, 0xb4, 0x8b, 0x2f, 0xdf, 0x6c, 0x14, 0xc8, 0x09, 0x01, 0xfa, 0x11, 0x6e, 0x84, + 0xd1, 0x90, 0x25, 0xa3, 0x3e, 0x8d, 0x98, 0xcf, 0xc2, 0x38, 0xf2, 0x7a, 0xe1, 0xd3, 0xc4, 0x4f, + 0x5e, 0x78, 0x43, 0xae, 0xa6, 0xcc, 0xd5, 0xe6, 0x1b, 0x4b, 0x5b, 0x9f, 0x35, 0x67, 0xb5, 0xa5, + 0x69, 0x4c, 0x52, 0x98, 0x29, 0x83, 0xb0, 0x4b, 0xae, 0x87, 0xf9, 0xc9, 0xfa, 0x6f, 0x12, 0x5c, + 0x9f, 0x51, 0x8c, 0x18, 0xfc, 0x3f, 0xc7, 0x5e, 0x76, 0xf4, 0x4f, 0xa7, 0x1a, 0xcb, 0x3a, 0x9e, + 0xeb, 0x2c, 0x6b, 0xc3, 0xd5, 0xe9, 0xd6, 0xd0, 0x43, 0x28, 0x9d, 0x3d, 0x7c, 0x7d, 0xf6, 0xe1, + 0xb9, 0x53, 0x92, 0x16, 0xd4, 0x8f, 0x96, 0xa1, 0xc8, 0xdf, 0xd1, 0x1e, 0x2c, 0x0a, 0x80, 0x17, + 0x06, 0xc2, 0xe9, 0x72, 0xfb, 0x0b, 0x2e, 0xf9, 0xfa, 0xcd, 0xc6, 0x83, 0x83, 0xf8, 0x1c, 0x5f, + 0xc8, 0x97, 0xa5, 0xd7, 0xa3, 0xfb, 0x2c, 0x4e, 0x5a, 0x61, 0xc4, 0x68, 0x12, 0xf9, 0xbd, 0x56, + 0xe0, 0x33, 0xbf, 0xe9, 0x72, 0x0e, 0x43, 0x27, 0x0b, 0x82, 0xcc, 0x08, 0x90, 0x03, 0x0b, 0x5c, + 0x89, 0xd3, 0xce, 0x09, 0xda, 0xcf, 0x33, 0xda, 0xad, 0x77, 0xa1, 0xe5, 0x16, 0x0d, 0x9d, 0x94, + 0x39, 0x95, 0x11, 0xa0, 0x0d, 0x58, 0x4a, 0xcd, 0x0e, 0x99, 0xcf, 0xa8, 0x32, 0x5f, 0x93, 0x1a, + 0x97, 0x08, 0x88, 0x90, 0xc3, 0x23, 0xe8, 0x7b, 0xa8, 0x0c, 0xfc, 0x84, 0x46, 0xcc, 0x1b, 0x8b, + 0x17, 0xff, 0xb5, 0xf8, 0x72, 0xca, 0xe8, 0xa4, 0x16, 0x10, 0x14, 0x23, 0xbf, 0x4f, 0x95, 0x92, + 0xd0, 0x16, 0xcf, 0xe8, 0x2b, 0x28, 0x3e, 0x0b, 0xa3, 0x40, 0x29, 0xd7, 0xa4, 0x46, 0x65, 0xeb, + 0xee, 0xc5, 0x53, 0x10, 0x3f, 0x76, 0xc2, 0x28, 0x20, 0xa2, 0x10, 0xb5, 0x60, 0x6d, 0xc8, 0xfc, + 0x84, 0x79, 0x2c, 0xec, 0x53, 0x6f, 0x14, 0x85, 0xcf, 0xbd, 0xc8, 0x8f, 0x62, 0x65, 0xa1, 0x26, + 0x35, 0xca, 0xe4, 0xb2, 0xc8, 0xb9, 0x61, 0x9f, 0x76, 0xa3, 0xf0, 0xb9, 0xe5, 0x47, 0x31, 0xba, + 0x0b, 0x88, 0x46, 0xc1, 0x79, 0xf8, 0xa2, 0x80, 0xaf, 0xd2, 0x28, 0x98, 0x00, 0xef, 0x02, 0xf8, + 0x8c, 0x25, 0xe1, 0xd3, 0x11, 0xa3, 0x43, 0xe5, 0x92, 0x58, 0x95, 0x5b, 0x17, 0xac, 0xe3, 0x0e, + 0x7d, 0xb1, 0xe7, 0xf7, 0x46, 0xe3, 0x7b, 0x78, 0x86, 0x00, 0x3d, 0x04, 0x25, 0x48, 0xe2, 0xc1, + 0x80, 0x06, 0xde, 0x69, 0xd4, 0xdb, 0x8f, 0x47, 0x11, 0x53, 0xa0, 0x26, 0x35, 0x56, 0xc8, 0xd5, + 0x2c, 0xaf, 0x9e, 0xa4, 0x35, 0x9e, 0x45, 0x5f, 0x43, 0x99, 0x1e, 0xd2, 0x88, 0x0d, 0x95, 0x25, + 0x61, 0xa2, 0xf1, 0x16, 0x9d, 0xc2, 0xbc, 0x80, 0x64, 0x75, 0xe8, 0x13, 0x58, 0x1b, 0x6b, 0xa7, + 0x91, 0x4c, 0x77, 0x59, 0xe8, 0xa2, 0x2c, 0x27, 0x6a, 0x32, 0xcd, 0x2f, 0xa1, 0xd4, 0x0b, 0xa3, + 0x67, 0x43, 0x65, 0x65, 0xc6, 0xb9, 0x27, 0x25, 0xcd, 0x30, 0x7a, 0x46, 0xd2, 0x2a, 0xd4, 0x84, + 0xff, 0x8d, 0x05, 0x45, 0x20, 0xd3, 0xab, 0x08, 0xbd, 0xcb, 0x59, 0x8a, 0x17, 0x64, 0x72, 0x6d, + 0x28, 0xf3, 0xdd, 0x1c, 0x0d, 0x95, 0x55, 0x71, 0xed, 0x37, 0x2f, 0xd0, 0x13, 0xd8, 0xac, 0xc9, + 0x59, 0xe5, 0xfa, 0xaf, 0x12, 0x94, 0xc4, 0x11, 0xd0, 0x26, 0x54, 0xce, 0x8d, 0x58, 0x12, 0x23, + 0x5e, 0x66, 0x67, 0xe7, 0x3b, 0x5e, 0xc9, 0xb9, 0x33, 0x2b, 0x39, 0x39, 0xf3, 0xf9, 0xf7, 0x39, + 0xf3, 0xe2, 0xac, 0x99, 0xaf, 0xff, 0x39, 0x07, 0x45, 0xde, 0x9f, 0xff, 0xd8, 0x87, 0x66, 0xb2, + 0xbf, 0xc5, 0xf7, 0xd9, 0xdf, 0xd2, 0xac, 0xfe, 0xd6, 0x7f, 0x91, 0x60, 0x71, 0xfc, 0x35, 0x41, + 0xd7, 0xe0, 0x8a, 0xd3, 0x51, 0x2d, 0x6f, 0xc7, 0xb0, 0x74, 0xaf, 0x6b, 0x39, 0x1d, 0xac, 0x19, + 0xdb, 0x06, 0xd6, 0xe5, 0x02, 0xba, 0x0a, 0xe8, 0x34, 0x65, 0x58, 0x2e, 0x26, 0x96, 0x6a, 0xca, + 0x12, 0x5a, 0x03, 0xf9, 0x34, 0xee, 0x60, 0xb2, 0x87, 0x89, 0x3c, 0x37, 0x19, 0xd5, 0x4c, 0x03, + 0x5b, 0xae, 0x3c, 0x3f, 0xc9, 0xd1, 0x21, 0xb6, 0xde, 0xd5, 0x30, 0x91, 0x8b, 0x93, 0x71, 0xcd, + 0xb6, 0x9c, 0xee, 0x2e, 0x26, 0x72, 0xa9, 0xfe, 0xf7, 0x02, 0x94, 0xd3, 0x0d, 0x47, 0x3f, 0xc0, + 0x6a, 0x40, 0x07, 0x09, 0xdd, 0xf7, 0x19, 0x0d, 0xbc, 0xfd, 0x38, 0x48, 0xff, 0x24, 0xa8, 0x5c, + 0xf4, 0x0b, 0x3b, 0x2d, 0x6f, 0xea, 0x27, 0xb5, 0x69, 0x40, 0x8b, 0x03, 0xda, 0x9e, 0x53, 0x24, + 0x52, 0x39, 0x65, 0xe5, 0x31, 0xa4, 0xc0, 0x42, 0x9f, 0x0e, 0x87, 0xfe, 0xc1, 0xf8, 0x3a, 0x8c, + 0x5f, 0x91, 0x06, 0x45, 0x21, 0x3b, 0x2f, 0x64, 0x5b, 0x6f, 0x25, 0x7b, 0x2a, 0x46, 0x44, 0x71, + 0xfd, 0x75, 0x09, 0xd6, 0xa6, 0x79, 0x41, 0x37, 0xe0, 0x9a, 0x8e, 0x3b, 0x04, 0x6b, 0xaa, 0x8b, + 0x75, 0xcf, 0x71, 0x55, 0xb7, 0xeb, 0x78, 0x9a, 0xad, 0x63, 0xcf, 0xde, 0x91, 0x0b, 0x68, 0x13, + 0x6a, 0x39, 0x69, 0x4d, 0xb5, 0x34, 0x6c, 0x9a, 0x58, 0x97, 0x25, 0xd4, 0x80, 0xcd, 0x1c, 0x54, + 0xd7, 0xda, 0xb1, 0xec, 0x6f, 0x2c, 0x0f, 0x13, 0x62, 0xf3, 0xf9, 0xdc, 0x85, 0x5b, 0x39, 0x48, + 0xc3, 0xda, 0x53, 0x4d, 0x43, 0xf7, 0x54, 0xf2, 0xa8, 0xbb, 0x9b, 0x8e, 0xed, 0x63, 0x68, 0xe4, + 0x80, 0x75, 0xac, 0xea, 0xa6, 0x61, 0x61, 0x0f, 0x3f, 0xd1, 0x30, 0xd6, 0xb1, 0x2e, 0x17, 0x67, + 0x58, 0xb5, 0x6c, 0xd7, 0xdb, 0xb6, 0xbb, 0x96, 0x2e, 0x97, 0xd0, 0x6d, 0xf8, 0x28, 0x07, 0xa5, + 0x9a, 0x04, 0xab, 0xfa, 0xb7, 0x1e, 0x7e, 0x62, 0x38, 0xae, 0x23, 0x97, 0x67, 0xc8, 0x77, 0x30, + 0xd9, 0x35, 0x1c, 0xc7, 0xb0, 0x2d, 0x4f, 0xc7, 0x16, 0xdf, 0xd3, 0x05, 0x74, 0x0f, 0x6e, 0xe7, + 0xa0, 0x09, 0x76, 0xec, 0x2e, 0xd1, 0xb8, 0xd9, 0xc7, 0x6a, 0xd7, 0x71, 0xb1, 0x2e, 0x2f, 0xa2, + 0x26, 0xdc, 0xc9, 0x81, 0x6f, 0xab, 0x86, 0x89, 0xf9, 0x9a, 0x62, 0xcd, 0xb6, 0x74, 0xc3, 0x35, + 0x6c, 0x4b, 0xbe, 0x84, 0xea, 0x50, 0xcd, 0xf3, 0xdd, 0xb6, 0x09, 0xe7, 0x04, 0x74, 0x0b, 0x3e, + 0xcc, 0x9b, 0x65, 0xd7, 0xf5, 0xec, 0x6d, 0x8f, 0xa8, 0xd6, 0x23, 0x2c, 0x2f, 0xcd, 0x9c, 0x97, + 0xb1, 0xdb, 0x31, 0x31, 0x1f, 0x00, 0xd6, 0xe5, 0xe5, 0x19, 0xed, 0x1a, 0x5f, 0xc5, 0x6c, 0xb4, + 0x2b, 0xe8, 0x26, 0xd4, 0x73, 0x49, 0xd5, 0x3d, 0xd5, 0x30, 0xd5, 0xb6, 0x89, 0xe5, 0xca, 0x8c, + 0x39, 0xe9, 0xaa, 0xab, 0x7a, 0xa6, 0xed, 0x38, 0xf2, 0x2a, 0xba, 0x03, 0x37, 0xf3, 0xd9, 0xba, + 0xee, 0x63, 0x6c, 0xb9, 0x86, 0xc8, 0xc9, 0x72, 0xdd, 0x02, 0x38, 0xb3, 0xd1, 0x57, 0xe0, 0xf2, + 0x24, 0xdc, 0xc1, 0xae, 0x5c, 0x40, 0x08, 0x2a, 0xe7, 0xb6, 0x5b, 0x3a, 0x0f, 0xcd, 0x96, 0xb4, + 0xfd, 0xb3, 0xf4, 0xf2, 0xa8, 0x2a, 0xbd, 0x3a, 0xaa, 0x4a, 0x7f, 0x1d, 0x55, 0xa5, 0x9f, 0x8e, + 0xab, 0x85, 0x57, 0xc7, 0xd5, 0xc2, 0xef, 0xc7, 0xd5, 0x02, 0x6c, 0x84, 0xf1, 0xcc, 0x0b, 0xd8, + 0x06, 0xf1, 0x9d, 0xef, 0xf0, 0x60, 0x47, 0xfa, 0xce, 0x7c, 0x87, 0x4f, 0x79, 0x6b, 0x02, 0x78, + 0x4f, 0xf0, 0xde, 0x3b, 0xa0, 0xd1, 0xc9, 0x7f, 0x51, 0x4f, 0xcb, 0x22, 0xf6, 0xe0, 0x9f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x70, 0x20, 0x6f, 0x05, 0x6c, 0x0d, 0x00, 0x00, +} + +func (m *ResourceSpans) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourceSpans) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceSpans) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.InstrumentationLibrarySpans) > 0 { + for iNdEx := len(m.InstrumentationLibrarySpans) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.InstrumentationLibrarySpans[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *InstrumentationLibrarySpans) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InstrumentationLibrarySpans) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InstrumentationLibrarySpans) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Spans) > 0 { + for iNdEx := len(m.Spans) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Spans[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.InstrumentationLibrary.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Span) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Span) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Span) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x7a + if m.DroppedLinksCount != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.DroppedLinksCount)) + i-- + dAtA[i] = 0x70 + } + if len(m.Links) > 0 { + for iNdEx := len(m.Links) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Links[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a + } + } + if m.DroppedEventsCount != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.DroppedEventsCount)) + i-- + dAtA[i] = 0x60 + } + if len(m.Events) > 0 { + for iNdEx := len(m.Events) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Events[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } + } + if m.DroppedAttributesCount != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.DroppedAttributesCount)) + i-- + dAtA[i] = 0x50 + } + if len(m.Attributes) > 0 { + for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + } + if m.EndTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.EndTimeUnixNano)) + i-- + dAtA[i] = 0x41 + } + if m.StartTimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.StartTimeUnixNano)) + i-- + dAtA[i] = 0x39 + } + if m.Kind != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.Kind)) + i-- + dAtA[i] = 0x30 + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintTrace(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x2a + } + { + size := m.ParentSpanId.Size() + i -= size + if _, err := m.ParentSpanId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.TraceState) > 0 { + i -= len(m.TraceState) + copy(dAtA[i:], m.TraceState) + i = encodeVarintTrace(dAtA, i, uint64(len(m.TraceState))) + i-- + dAtA[i] = 0x1a + } + { + size := m.SpanId.Size() + i -= size + if _, err := m.SpanId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.TraceId.Size() + i -= size + if _, err := m.TraceId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Span_Event) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Span_Event) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Span_Event) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DroppedAttributesCount != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.DroppedAttributesCount)) + i-- + dAtA[i] = 0x20 + } + if len(m.Attributes) > 0 { + for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintTrace(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x12 + } + if m.TimeUnixNano != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TimeUnixNano)) + i-- + dAtA[i] = 0x9 + } + return len(dAtA) - i, nil +} + +func (m *Span_Link) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Span_Link) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Span_Link) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DroppedAttributesCount != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.DroppedAttributesCount)) + i-- + dAtA[i] = 0x28 + } + if len(m.Attributes) > 0 { + for iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.TraceState) > 0 { + i -= len(m.TraceState) + copy(dAtA[i:], m.TraceState) + i = encodeVarintTrace(dAtA, i, uint64(len(m.TraceState))) + i-- + dAtA[i] = 0x1a + } + { + size := m.SpanId.Size() + i -= size + if _, err := m.SpanId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.TraceId.Size() + i -= size + if _, err := m.TraceId.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Code != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.Code)) + i-- + dAtA[i] = 0x18 + } + if len(m.Message) > 0 { + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintTrace(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x12 + } + if m.DeprecatedCode != 0 { + i = encodeVarintTrace(dAtA, i, uint64(m.DeprecatedCode)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTrace(dAtA []byte, offset int, v uint64) int { + offset -= sovTrace(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ResourceSpans) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Resource.Size() + n += 1 + l + sovTrace(uint64(l)) + if len(m.InstrumentationLibrarySpans) > 0 { + for _, e := range m.InstrumentationLibrarySpans { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + return n +} + +func (m *InstrumentationLibrarySpans) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.InstrumentationLibrary.Size() + n += 1 + l + sovTrace(uint64(l)) + if len(m.Spans) > 0 { + for _, e := range m.Spans { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + return n +} + +func (m *Span) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.TraceId.Size() + n += 1 + l + sovTrace(uint64(l)) + l = m.SpanId.Size() + n += 1 + l + sovTrace(uint64(l)) + l = len(m.TraceState) + if l > 0 { + n += 1 + l + sovTrace(uint64(l)) + } + l = m.ParentSpanId.Size() + n += 1 + l + sovTrace(uint64(l)) + l = len(m.Name) + if l > 0 { + n += 1 + l + sovTrace(uint64(l)) + } + if m.Kind != 0 { + n += 1 + sovTrace(uint64(m.Kind)) + } + if m.StartTimeUnixNano != 0 { + n += 9 + } + if m.EndTimeUnixNano != 0 { + n += 9 + } + if len(m.Attributes) > 0 { + for _, e := range m.Attributes { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.DroppedAttributesCount != 0 { + n += 1 + sovTrace(uint64(m.DroppedAttributesCount)) + } + if len(m.Events) > 0 { + for _, e := range m.Events { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.DroppedEventsCount != 0 { + n += 1 + sovTrace(uint64(m.DroppedEventsCount)) + } + if len(m.Links) > 0 { + for _, e := range m.Links { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.DroppedLinksCount != 0 { + n += 1 + sovTrace(uint64(m.DroppedLinksCount)) + } + l = m.Status.Size() + n += 1 + l + sovTrace(uint64(l)) + return n +} + +func (m *Span_Event) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TimeUnixNano != 0 { + n += 9 + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovTrace(uint64(l)) + } + if len(m.Attributes) > 0 { + for _, e := range m.Attributes { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.DroppedAttributesCount != 0 { + n += 1 + sovTrace(uint64(m.DroppedAttributesCount)) + } + return n +} + +func (m *Span_Link) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.TraceId.Size() + n += 1 + l + sovTrace(uint64(l)) + l = m.SpanId.Size() + n += 1 + l + sovTrace(uint64(l)) + l = len(m.TraceState) + if l > 0 { + n += 1 + l + sovTrace(uint64(l)) + } + if len(m.Attributes) > 0 { + for _, e := range m.Attributes { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.DroppedAttributesCount != 0 { + n += 1 + sovTrace(uint64(m.DroppedAttributesCount)) + } + return n +} + +func (m *Status) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DeprecatedCode != 0 { + n += 1 + sovTrace(uint64(m.DeprecatedCode)) + } + l = len(m.Message) + if l > 0 { + n += 1 + l + sovTrace(uint64(l)) + } + if m.Code != 0 { + n += 1 + sovTrace(uint64(m.Code)) + } + return n +} + +func sovTrace(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTrace(x uint64) (n int) { + return sovTrace(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ResourceSpans) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResourceSpans: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResourceSpans: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstrumentationLibrarySpans", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstrumentationLibrarySpans = append(m.InstrumentationLibrarySpans, &InstrumentationLibrarySpans{}) + if err := m.InstrumentationLibrarySpans[len(m.InstrumentationLibrarySpans)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InstrumentationLibrarySpans) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InstrumentationLibrarySpans: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InstrumentationLibrarySpans: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstrumentationLibrary", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InstrumentationLibrary.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spans", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Spans = append(m.Spans, &Span{}) + if err := m.Spans[len(m.Spans)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Span) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Span: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Span: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TraceId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SpanId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceState", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TraceState = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ParentSpanId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ParentSpanId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + m.Kind = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Kind |= Span_SpanKind(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType) + } + m.StartTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.StartTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 8: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field EndTimeUnixNano", wireType) + } + m.EndTimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.EndTimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attributes = append(m.Attributes, v11.KeyValue{}) + if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType) + } + m.DroppedAttributesCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedAttributesCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Events", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Events = append(m.Events, &Span_Event{}) + if err := m.Events[len(m.Events)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedEventsCount", wireType) + } + m.DroppedEventsCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedEventsCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Links", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Links = append(m.Links, &Span_Link{}) + if err := m.Links[len(m.Links)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedLinksCount", wireType) + } + m.DroppedLinksCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedLinksCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Span_Event) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Event: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Event: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType) + } + m.TimeUnixNano = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.TimeUnixNano = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attributes = append(m.Attributes, v11.KeyValue{}) + if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType) + } + m.DroppedAttributesCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedAttributesCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Span_Link) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Link: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Link: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TraceId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SpanId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TraceState", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TraceState = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attributes = append(m.Attributes, v11.KeyValue{}) + if err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType) + } + m.DroppedAttributesCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DroppedAttributesCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedCode", wireType) + } + m.DeprecatedCode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DeprecatedCode |= Status_DeprecatedStatusCode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) + } + m.Code = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Code |= Status_StatusCode(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTrace(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTrace + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTrace + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTrace + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTrace = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTrace = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTrace = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/otel_collector/internal/data/spanid.go b/internal/otel_collector/internal/data/spanid.go new file mode 100644 index 00000000000..0a9c1f605a0 --- /dev/null +++ b/internal/otel_collector/internal/data/spanid.go @@ -0,0 +1,104 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package data + +import ( + "encoding/hex" + "errors" +) + +const spanIDSize = 8 + +var errInvalidSpanIDSize = errors.New("invalid length for SpanID") + +// SpanID is a custom data type that is used for all span_id fields in OTLP +// Protobuf messages. +type SpanID struct { + id [spanIDSize]byte +} + +// NewSpanID creates a SpanID from a byte slice. +func NewSpanID(bytes [8]byte) SpanID { + return SpanID{id: bytes} +} + +// HexString returns hex representation of the ID. +func (sid SpanID) HexString() string { + if !sid.IsValid() { + return "" + } + return hex.EncodeToString(sid.id[:]) +} + +// Size returns the size of the data to serialize. +func (sid *SpanID) Size() int { + if !sid.IsValid() { + return 0 + } + return spanIDSize +} + +// Equal returns true if ids are equal. +func (sid SpanID) Equal(that SpanID) bool { + return sid.id == that.id +} + +// IsValid returns true if id contains at least one non-zero byte. +func (sid SpanID) IsValid() bool { + return sid.id != [8]byte{} +} + +// Bytes returns the byte array representation of the SpanID. +func (sid SpanID) Bytes() [8]byte { + return sid.id +} + +// MarshalTo converts trace ID into a binary representation. Called by Protobuf serialization. +func (sid *SpanID) MarshalTo(data []byte) (n int, err error) { + if !sid.IsValid() { + return 0, nil + } + return marshalBytes(data, sid.id[:]) +} + +// Unmarshal inflates this trace ID from binary representation. Called by Protobuf serialization. +func (sid *SpanID) Unmarshal(data []byte) error { + if len(data) == 0 { + sid.id = [8]byte{} + return nil + } + + if len(data) != spanIDSize { + return errInvalidSpanIDSize + } + + copy(sid.id[:], data) + return nil +} + +// MarshalJSON converts SpanID into a hex string enclosed in quotes. +func (sid SpanID) MarshalJSON() ([]byte, error) { + if !sid.IsValid() { + return []byte(`""`), nil + } + return marshalJSON(sid.id[:]) +} + +// UnmarshalJSON decodes SpanID from hex string, possibly enclosed in quotes. +// Called by Protobuf JSON deserialization. +func (sid *SpanID) UnmarshalJSON(data []byte) error { + sid.id = [8]byte{} + return unmarshalJSON(sid.id[:], data) +} diff --git a/internal/otel_collector/internal/data/spanid_test.go b/internal/otel_collector/internal/data/spanid_test.go new file mode 100644 index 00000000000..5d4526649f6 --- /dev/null +++ b/internal/otel_collector/internal/data/spanid_test.go @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package data + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewSpanID(t *testing.T) { + sid := NewSpanID([8]byte{}) + assert.EqualValues(t, [8]byte{}, sid.id) + assert.EqualValues(t, 0, sid.Size()) + + b := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} + sid = NewSpanID(b) + assert.EqualValues(t, b, sid.id) + assert.EqualValues(t, 8, sid.Size()) +} + +func TestSpanIDHexString(t *testing.T) { + sid := NewSpanID([8]byte{}) + assert.EqualValues(t, "", sid.HexString()) + + sid = NewSpanID([8]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23}) + assert.EqualValues(t, "1223ad1223ad1223", sid.HexString()) +} + +func TestSpanIDEqual(t *testing.T) { + sid := NewSpanID([8]byte{}) + assert.True(t, sid.Equal(sid)) + assert.True(t, sid.Equal(NewSpanID([8]byte{}))) + assert.False(t, sid.Equal(NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))) + + sid = NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + assert.True(t, sid.Equal(sid)) + assert.False(t, sid.Equal(NewSpanID([8]byte{}))) + assert.True(t, sid.Equal(NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))) +} + +func TestSpanIDMarshal(t *testing.T) { + buf := make([]byte, 10) + + sid := NewSpanID([8]byte{}) + n, err := sid.MarshalTo(buf) + assert.EqualValues(t, 0, n) + assert.NoError(t, err) + + sid = NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + n, err = sid.MarshalTo(buf) + assert.NoError(t, err) + assert.EqualValues(t, 8, n) + assert.EqualValues(t, []byte{1, 2, 3, 4, 5, 6, 7, 8}, buf[0:8]) + + _, err = sid.MarshalTo(buf[0:1]) + assert.Error(t, err) +} + +func TestSpanIDMarshalJSON(t *testing.T) { + sid := NewSpanID([8]byte{}) + json, err := sid.MarshalJSON() + assert.EqualValues(t, []byte(`""`), json) + assert.NoError(t, err) + + sid = NewSpanID([8]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23}) + json, err = sid.MarshalJSON() + assert.EqualValues(t, []byte(`"1223ad1223ad1223"`), json) + assert.NoError(t, err) +} + +func TestSpanIDUnmarshal(t *testing.T) { + buf := []byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23} + + sid := SpanID{} + err := sid.Unmarshal(buf[0:8]) + assert.NoError(t, err) + assert.EqualValues(t, [8]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23}, sid.id) + + err = sid.Unmarshal(buf[0:0]) + assert.NoError(t, err) + assert.EqualValues(t, [8]byte{}, sid.id) + + err = sid.Unmarshal(nil) + assert.NoError(t, err) + assert.EqualValues(t, [8]byte{}, sid.id) + + err = sid.Unmarshal(buf[0:3]) + assert.Error(t, err) +} + +func TestSpanIDUnmarshalJSON(t *testing.T) { + sid := SpanID{} + err := sid.UnmarshalJSON([]byte(`""`)) + assert.NoError(t, err) + assert.EqualValues(t, [8]byte{}, sid.id) + + err = sid.UnmarshalJSON([]byte(`"1234567812345678"`)) + assert.NoError(t, err) + assert.EqualValues(t, [8]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}, sid.id) + + err = sid.UnmarshalJSON([]byte(`1234567812345678`)) + assert.NoError(t, err) + assert.EqualValues(t, [8]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}, sid.id) + + err = sid.UnmarshalJSON([]byte(`"nothex"`)) + assert.Error(t, err) + + err = sid.UnmarshalJSON([]byte(`"1"`)) + assert.Error(t, err) + + err = sid.UnmarshalJSON([]byte(`"123"`)) + assert.Error(t, err) + + err = sid.UnmarshalJSON([]byte(`"`)) + assert.Error(t, err) +} diff --git a/internal/otel_collector/internal/data/traceid.go b/internal/otel_collector/internal/data/traceid.go new file mode 100644 index 00000000000..c3d7f2f2a02 --- /dev/null +++ b/internal/otel_collector/internal/data/traceid.go @@ -0,0 +1,106 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package data + +import ( + "encoding/hex" + "errors" +) + +const traceIDSize = 16 + +var errInvalidTraceIDSize = errors.New("invalid length for SpanID") + +// TraceID is a custom data type that is used for all trace_id fields in OTLP +// Protobuf messages. +type TraceID struct { + id [traceIDSize]byte +} + +// NewTraceID creates a TraceID from a byte slice. +func NewTraceID(bytes [16]byte) TraceID { + return TraceID{ + id: bytes, + } +} + +// HexString returns hex representation of the ID. +func (tid TraceID) HexString() string { + if !tid.IsValid() { + return "" + } + return hex.EncodeToString(tid.id[:]) +} + +// Size returns the size of the data to serialize. +func (tid *TraceID) Size() int { + if !tid.IsValid() { + return 0 + } + return traceIDSize +} + +// Equal returns true if ids are equal. +func (tid TraceID) Equal(that TraceID) bool { + return tid.id == that.id +} + +// IsValid returns true if id contains at leas one non-zero byte. +func (tid TraceID) IsValid() bool { + return tid.id != [16]byte{} +} + +// Bytes returns the byte array representation of the TraceID. +func (tid TraceID) Bytes() [16]byte { + return tid.id +} + +// MarshalTo converts trace ID into a binary representation. Called by Protobuf serialization. +func (tid *TraceID) MarshalTo(data []byte) (n int, err error) { + if !tid.IsValid() { + return 0, nil + } + return marshalBytes(data, tid.id[:]) +} + +// Unmarshal inflates this trace ID from binary representation. Called by Protobuf serialization. +func (tid *TraceID) Unmarshal(data []byte) error { + if len(data) == 0 { + tid.id = [16]byte{} + return nil + } + + if len(data) != traceIDSize { + return errInvalidTraceIDSize + } + + copy(tid.id[:], data) + return nil +} + +// MarshalJSON converts trace id into a hex string enclosed in quotes. +func (tid TraceID) MarshalJSON() ([]byte, error) { + if !tid.IsValid() { + return []byte(`""`), nil + } + return marshalJSON(tid.id[:]) +} + +// UnmarshalJSON inflates trace id from hex string, possibly enclosed in quotes. +// Called by Protobuf JSON deserialization. +func (tid *TraceID) UnmarshalJSON(data []byte) error { + tid.id = [16]byte{} + return unmarshalJSON(tid.id[:], data) +} diff --git a/internal/otel_collector/internal/data/traceid_test.go b/internal/otel_collector/internal/data/traceid_test.go new file mode 100644 index 00000000000..1a16c455dc2 --- /dev/null +++ b/internal/otel_collector/internal/data/traceid_test.go @@ -0,0 +1,130 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package data + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewTraceID(t *testing.T) { + tid := NewTraceID([16]byte{}) + assert.EqualValues(t, [16]byte{}, tid.id) + assert.EqualValues(t, 0, tid.Size()) + + b := [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78} + tid = NewTraceID(b) + assert.EqualValues(t, b, tid.id) + assert.EqualValues(t, 16, tid.Size()) +} + +func TestTraceIDHexString(t *testing.T) { + tid := NewTraceID([16]byte{}) + assert.EqualValues(t, "", tid.HexString()) + + tid = NewTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}) + assert.EqualValues(t, "12345678123456781234567812345678", tid.HexString()) +} + +func TestTraceIDEqual(t *testing.T) { + tid := NewTraceID([16]byte{}) + assert.True(t, tid.Equal(tid)) + assert.True(t, tid.Equal(NewTraceID([16]byte{}))) + assert.False(t, tid.Equal(NewTraceID([16]byte{1}))) + + tid = NewTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}) + assert.True(t, tid.Equal(tid)) + assert.False(t, tid.Equal(NewTraceID([16]byte{}))) + assert.True(t, tid.Equal(NewTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}))) +} + +func TestTraceIDMarshal(t *testing.T) { + buf := make([]byte, 20) + + tid := NewTraceID([16]byte{}) + n, err := tid.MarshalTo(buf) + assert.EqualValues(t, 0, n) + assert.NoError(t, err) + + tid = NewTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}) + n, err = tid.MarshalTo(buf) + assert.EqualValues(t, 16, n) + assert.EqualValues(t, []byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}, buf[0:16]) + assert.NoError(t, err) + + _, err = tid.MarshalTo(buf[0:1]) + assert.Error(t, err) +} + +func TestTraceIDMarshalJSON(t *testing.T) { + tid := NewTraceID([16]byte{}) + json, err := tid.MarshalJSON() + assert.EqualValues(t, []byte(`""`), json) + assert.NoError(t, err) + + tid = NewTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}) + json, err = tid.MarshalJSON() + assert.EqualValues(t, []byte(`"12345678123456781234567812345678"`), json) + assert.NoError(t, err) +} + +func TestTraceIDUnmarshal(t *testing.T) { + buf := [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78} + + tid := TraceID{} + err := tid.Unmarshal(buf[0:16]) + assert.NoError(t, err) + assert.EqualValues(t, buf, tid.id) + + err = tid.Unmarshal(buf[0:0]) + assert.NoError(t, err) + assert.EqualValues(t, [16]byte{}, tid.id) + + err = tid.Unmarshal(nil) + assert.NoError(t, err) + assert.EqualValues(t, [16]byte{}, tid.id) +} + +func TestTraceIDUnmarshalJSON(t *testing.T) { + tid := NewTraceID([16]byte{}) + err := tid.UnmarshalJSON([]byte(`""`)) + assert.NoError(t, err) + assert.EqualValues(t, [16]byte{}, tid.id) + + err = tid.UnmarshalJSON([]byte(`""""`)) + assert.Error(t, err) + + tidBytes := [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78} + err = tid.UnmarshalJSON([]byte(`"12345678123456781234567812345678"`)) + assert.NoError(t, err) + assert.EqualValues(t, tidBytes, tid.id) + + err = tid.UnmarshalJSON([]byte(`12345678123456781234567812345678`)) + assert.NoError(t, err) + assert.EqualValues(t, tidBytes, tid.id) + + err = tid.UnmarshalJSON([]byte(`"nothex"`)) + assert.Error(t, err) + + err = tid.UnmarshalJSON([]byte(`"1"`)) + assert.Error(t, err) + + err = tid.UnmarshalJSON([]byte(`"123"`)) + assert.Error(t, err) + + err = tid.UnmarshalJSON([]byte(`"`)) + assert.Error(t, err) +} diff --git a/internal/otel_collector/internal/goldendataset/generator_commons.go b/internal/otel_collector/internal/goldendataset/generator_commons.go new file mode 100644 index 00000000000..cedbed1418e --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/generator_commons.go @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "encoding/csv" + "io" + "os" + "path/filepath" + + "github.com/spf13/cast" + + "go.opentelemetry.io/collector/internal/data" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" +) + +func convertMapToAttributeKeyValues(attrsMap map[string]interface{}) []otlpcommon.KeyValue { + if attrsMap == nil { + return nil + } + attrList := make([]otlpcommon.KeyValue, len(attrsMap)) + index := 0 + for key, value := range attrsMap { + attrList[index] = constructAttributeKeyValue(key, value) + index++ + } + return attrList +} + +func constructAttributeKeyValue(key string, value interface{}) otlpcommon.KeyValue { + var attr otlpcommon.KeyValue + switch val := value.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + attr = otlpcommon.KeyValue{ + Key: key, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_IntValue{IntValue: cast.ToInt64(val)}}, + } + case float32, float64: + attr = otlpcommon.KeyValue{ + Key: key, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_DoubleValue{DoubleValue: cast.ToFloat64(val)}}, + } + case bool: + attr = otlpcommon.KeyValue{ + Key: key, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_BoolValue{BoolValue: cast.ToBool(val)}}, + } + case *otlpcommon.ArrayValue: + attr = otlpcommon.KeyValue{ + Key: key, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: val}}, + } + case *otlpcommon.KeyValueList: + attr = otlpcommon.KeyValue{ + Key: key, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: val}}, + } + default: + attr = otlpcommon.KeyValue{ + Key: key, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: val.(string)}}, + } + } + return attr +} + +func loadPictOutputFile(fileName string) ([][]string, error) { + file, err := os.Open(filepath.Clean(fileName)) + if err != nil { + return nil, err + } + defer func() { + cerr := file.Close() + if err == nil { + err = cerr + } + }() + + reader := csv.NewReader(file) + reader.Comma = '\t' + + return reader.ReadAll() +} + +func generateTraceID(random io.Reader) data.TraceID { + var r [16]byte + _, err := random.Read(r[:]) + if err != nil { + panic(err) + } + return data.NewTraceID(r) +} + +func generateSpanID(random io.Reader) data.SpanID { + var r [8]byte + _, err := random.Read(r[:]) + if err != nil { + panic(err) + } + return data.NewSpanID(r) +} diff --git a/internal/otel_collector/internal/goldendataset/metric_gen.go b/internal/otel_collector/internal/goldendataset/metric_gen.go new file mode 100644 index 00000000000..76e376647f6 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/metric_gen.go @@ -0,0 +1,269 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "fmt" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Simple utilities for generating metrics for testing + +// MetricCfg holds parameters for generating dummy metrics for testing. Set values on this struct to generate +// metrics with the corresponding number/type of attributes and pass into MetricDataFromCfg to generate metrics. +type MetricCfg struct { + // The type of metric to generate + MetricDescriptorType pdata.MetricDataType + // If MetricDescriptorType is one of the Sum, this describes if the sum is monotonic or not. + IsMonotonicSum bool + // A prefix for every metric name + MetricNamePrefix string + // The number of instrumentation library metrics per resource + NumILMPerResource int + // The size of the MetricSlice and number of Metrics + NumMetricsPerILM int + // The number of labels on the LabelsMap associated with each point + NumPtLabels int + // The number of points to generate per Metric + NumPtsPerMetric int + // The number of Attributes to insert into each Resource's AttributesMap + NumResourceAttrs int + // The number of ResourceMetrics for the single MetricData generated + NumResourceMetrics int + // The base value for each point + PtVal int + // The start time for each point + StartTime uint64 + // The duration of the steps between each generated point starting at StartTime + StepSize uint64 +} + +// DefaultCfg produces a MetricCfg with default values. These should be good enough to produce sane +// (but boring) metrics, and can be used as a starting point for making alterations. +func DefaultCfg() MetricCfg { + return MetricCfg{ + MetricDescriptorType: pdata.MetricDataTypeIntGauge, + MetricNamePrefix: "", + NumILMPerResource: 1, + NumMetricsPerILM: 1, + NumPtLabels: 1, + NumPtsPerMetric: 1, + NumResourceAttrs: 1, + NumResourceMetrics: 1, + PtVal: 1, + StartTime: 940000000000000000, + StepSize: 42, + } +} + +// DefaultMetricData produces MetricData with a default config. +func DefaultMetricData() pdata.Metrics { + return MetricDataFromCfg(DefaultCfg()) +} + +// MetricDataFromCfg produces MetricData with the passed-in config. +func MetricDataFromCfg(cfg MetricCfg) pdata.Metrics { + return newMetricGenerator().genMetricDataFromCfg(cfg) +} + +type metricGenerator struct { + metricID int +} + +func newMetricGenerator() *metricGenerator { + return &metricGenerator{} +} + +func (g *metricGenerator) genMetricDataFromCfg(cfg MetricCfg) pdata.Metrics { + md := pdata.NewMetrics() + rms := md.ResourceMetrics() + rms.Resize(cfg.NumResourceMetrics) + for i := 0; i < cfg.NumResourceMetrics; i++ { + rm := rms.At(i) + resource := rm.Resource() + for j := 0; j < cfg.NumResourceAttrs; j++ { + resource.Attributes().Insert( + fmt.Sprintf("resource-attr-name-%d", j), + pdata.NewAttributeValueString(fmt.Sprintf("resource-attr-val-%d", j)), + ) + } + g.populateIlm(cfg, rm) + } + return md +} + +func (g *metricGenerator) populateIlm(cfg MetricCfg, rm pdata.ResourceMetrics) { + ilms := rm.InstrumentationLibraryMetrics() + ilms.Resize(cfg.NumILMPerResource) + for i := 0; i < cfg.NumILMPerResource; i++ { + ilm := ilms.At(i) + g.populateMetrics(cfg, ilm) + } +} + +func (g *metricGenerator) populateMetrics(cfg MetricCfg, ilm pdata.InstrumentationLibraryMetrics) { + metrics := ilm.Metrics() + metrics.Resize(cfg.NumMetricsPerILM) + for i := 0; i < cfg.NumMetricsPerILM; i++ { + metric := metrics.At(i) + g.populateMetricDesc(cfg, metric) + switch cfg.MetricDescriptorType { + case pdata.MetricDataTypeIntGauge: + metric.SetDataType(pdata.MetricDataTypeIntGauge) + populateIntPoints(cfg, metric.IntGauge().DataPoints()) + case pdata.MetricDataTypeDoubleGauge: + metric.SetDataType(pdata.MetricDataTypeDoubleGauge) + populateDoublePoints(cfg, metric.DoubleGauge().DataPoints()) + case pdata.MetricDataTypeIntSum: + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(cfg.IsMonotonicSum) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + populateIntPoints(cfg, sum.DataPoints()) + case pdata.MetricDataTypeDoubleSum: + metric.SetDataType(pdata.MetricDataTypeDoubleSum) + sum := metric.DoubleSum() + sum.SetIsMonotonic(cfg.IsMonotonicSum) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + populateDoublePoints(cfg, sum.DataPoints()) + case pdata.MetricDataTypeIntHistogram: + metric.SetDataType(pdata.MetricDataTypeIntHistogram) + histo := metric.IntHistogram() + histo.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + populateIntHistogram(cfg, histo) + case pdata.MetricDataTypeDoubleHistogram: + metric.SetDataType(pdata.MetricDataTypeDoubleHistogram) + histo := metric.DoubleHistogram() + histo.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + populateDoubleHistogram(cfg, histo) + } + } +} + +func (g *metricGenerator) populateMetricDesc(cfg MetricCfg, metric pdata.Metric) { + metric.SetName(fmt.Sprintf("%smetric_%d", cfg.MetricNamePrefix, g.metricID)) + g.metricID++ + metric.SetDescription("my-md-description") + metric.SetUnit("my-md-units") +} + +func populateIntPoints(cfg MetricCfg, pts pdata.IntDataPointSlice) { + pts.Resize(cfg.NumPtsPerMetric) + for i := 0; i < cfg.NumPtsPerMetric; i++ { + pt := pts.At(i) + pt.SetStartTime(pdata.TimestampUnixNano(cfg.StartTime)) + pt.SetTimestamp(getTimestamp(cfg.StartTime, cfg.StepSize, i)) + pt.SetValue(int64(cfg.PtVal + i)) + populatePtLabels(cfg, pt.LabelsMap()) + } +} + +func populateDoublePoints(cfg MetricCfg, pts pdata.DoubleDataPointSlice) { + pts.Resize(cfg.NumPtsPerMetric) + for i := 0; i < cfg.NumPtsPerMetric; i++ { + pt := pts.At(i) + pt.SetStartTime(pdata.TimestampUnixNano(cfg.StartTime)) + pt.SetTimestamp(getTimestamp(cfg.StartTime, cfg.StepSize, i)) + pt.SetValue(float64(cfg.PtVal + i)) + populatePtLabels(cfg, pt.LabelsMap()) + } +} + +func populateDoubleHistogram(cfg MetricCfg, dh pdata.DoubleHistogram) { + pts := dh.DataPoints() + pts.Resize(cfg.NumPtsPerMetric) + for i := 0; i < cfg.NumPtsPerMetric; i++ { + pt := pts.At(i) + pt.SetStartTime(pdata.TimestampUnixNano(cfg.StartTime)) + ts := getTimestamp(cfg.StartTime, cfg.StepSize, i) + pt.SetTimestamp(ts) + populatePtLabels(cfg, pt.LabelsMap()) + setDoubleHistogramBounds(pt, 1, 2, 3, 4, 5) + addDoubleHistogramVal(pt, 1) + for i := 0; i < cfg.PtVal; i++ { + addDoubleHistogramVal(pt, 3) + } + addDoubleHistogramVal(pt, 5) + } +} + +func setDoubleHistogramBounds(hdp pdata.DoubleHistogramDataPoint, bounds ...float64) { + hdp.SetBucketCounts(make([]uint64, len(bounds))) + hdp.SetExplicitBounds(bounds) +} + +func addDoubleHistogramVal(hdp pdata.DoubleHistogramDataPoint, val float64) { + hdp.SetCount(hdp.Count() + 1) + hdp.SetSum(hdp.Sum() + val) + buckets := hdp.BucketCounts() + bounds := hdp.ExplicitBounds() + for i := 0; i < len(bounds); i++ { + bound := bounds[i] + if val <= bound { + buckets[i]++ + break + } + } +} + +func populateIntHistogram(cfg MetricCfg, dh pdata.IntHistogram) { + pts := dh.DataPoints() + pts.Resize(cfg.NumPtsPerMetric) + for i := 0; i < cfg.NumPtsPerMetric; i++ { + pt := pts.At(i) + pt.SetStartTime(pdata.TimestampUnixNano(cfg.StartTime)) + ts := getTimestamp(cfg.StartTime, cfg.StepSize, i) + pt.SetTimestamp(ts) + populatePtLabels(cfg, pt.LabelsMap()) + setIntHistogramBounds(pt, 1, 2, 3, 4, 5) + addIntHistogramVal(pt, 1) + for i := 0; i < cfg.PtVal; i++ { + addIntHistogramVal(pt, 3) + } + addIntHistogramVal(pt, 5) + } +} + +func setIntHistogramBounds(hdp pdata.IntHistogramDataPoint, bounds ...float64) { + hdp.SetBucketCounts(make([]uint64, len(bounds))) + hdp.SetExplicitBounds(bounds) +} + +func addIntHistogramVal(hdp pdata.IntHistogramDataPoint, val int64) { + hdp.SetCount(hdp.Count() + 1) + hdp.SetSum(hdp.Sum() + val) + buckets := hdp.BucketCounts() + bounds := hdp.ExplicitBounds() + for i := 0; i < len(bounds); i++ { + bound := bounds[i] + if float64(val) <= bound { + buckets[i]++ + break + } + } +} + +func populatePtLabels(cfg MetricCfg, lm pdata.StringMap) { + for i := 0; i < cfg.NumPtLabels; i++ { + k := fmt.Sprintf("pt-label-key-%d", i) + v := fmt.Sprintf("pt-label-val-%d", i) + lm.Insert(k, v) + } +} + +func getTimestamp(startTime uint64, stepSize uint64, i int) pdata.TimestampUnixNano { + return pdata.TimestampUnixNano(startTime + (stepSize * uint64(i+1))) +} diff --git a/internal/otel_collector/internal/goldendataset/metric_gen_test.go b/internal/otel_collector/internal/goldendataset/metric_gen_test.go new file mode 100644 index 00000000000..360f9e50a08 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/metric_gen_test.go @@ -0,0 +1,130 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestGenDefault(t *testing.T) { + md := DefaultMetricData() + mCount, ptCount := md.MetricAndDataPointCount() + require.Equal(t, 1, mCount) + require.Equal(t, 1, ptCount) + rms := md.ResourceMetrics() + rm := rms.At(0) + resource := rm.Resource() + rattrs := resource.Attributes() + rattrs.Len() + require.Equal(t, 1, rattrs.Len()) + val, _ := rattrs.Get("resource-attr-name-0") + require.Equal(t, "resource-attr-val-0", val.StringVal()) + ilms := rm.InstrumentationLibraryMetrics() + require.Equal(t, 1, ilms.Len()) + ms := ilms.At(0).Metrics() + require.Equal(t, 1, ms.Len()) + pdm := ms.At(0) + require.Equal(t, "metric_0", pdm.Name()) + require.Equal(t, "my-md-description", pdm.Description()) + require.Equal(t, "my-md-units", pdm.Unit()) + + require.Equal(t, pdata.MetricDataTypeIntGauge, pdm.DataType()) + pts := pdm.IntGauge().DataPoints() + require.Equal(t, 1, pts.Len()) + pt := pts.At(0) + + require.Equal(t, 1, pt.LabelsMap().Len()) + ptLabels, _ := pt.LabelsMap().Get("pt-label-key-0") + require.Equal(t, "pt-label-val-0", ptLabels) + + require.EqualValues(t, 940000000000000000, pt.StartTime()) + require.EqualValues(t, 940000000000000042, pt.Timestamp()) + require.EqualValues(t, 1, pt.Value()) +} + +func TestDoubleHistogramFunctions(t *testing.T) { + pt := pdata.NewDoubleHistogramDataPoint() + setDoubleHistogramBounds(pt, 1, 2, 3, 4, 5) + require.Equal(t, 5, len(pt.ExplicitBounds())) + require.Equal(t, 5, len(pt.BucketCounts())) + + addDoubleHistogramVal(pt, 1) + require.EqualValues(t, 1, pt.Count()) + require.EqualValues(t, 1, pt.Sum()) + require.EqualValues(t, 1, pt.BucketCounts()[0]) + + addDoubleHistogramVal(pt, 2) + require.EqualValues(t, 2, pt.Count()) + require.EqualValues(t, 3, pt.Sum()) + require.EqualValues(t, 1, pt.BucketCounts()[1]) + + addDoubleHistogramVal(pt, 2) + require.EqualValues(t, 3, pt.Count()) + require.EqualValues(t, 5, pt.Sum()) + require.EqualValues(t, 2, pt.BucketCounts()[1]) +} + +func TestIntHistogramFunctions(t *testing.T) { + pt := pdata.NewIntHistogramDataPoint() + setIntHistogramBounds(pt, 1, 2, 3, 4, 5) + require.Equal(t, 5, len(pt.ExplicitBounds())) + require.Equal(t, 5, len(pt.BucketCounts())) + + addIntHistogramVal(pt, 1) + require.EqualValues(t, 1, pt.Count()) + require.EqualValues(t, 1, pt.Sum()) + require.EqualValues(t, 1, pt.BucketCounts()[0]) + + addIntHistogramVal(pt, 2) + require.EqualValues(t, 2, pt.Count()) + require.EqualValues(t, 3, pt.Sum()) + require.EqualValues(t, 1, pt.BucketCounts()[1]) + + addIntHistogramVal(pt, 2) + require.EqualValues(t, 3, pt.Count()) + require.EqualValues(t, 5, pt.Sum()) + require.EqualValues(t, 2, pt.BucketCounts()[1]) +} + +func TestGenDoubleHistogram(t *testing.T) { + cfg := DefaultCfg() + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleHistogram + cfg.PtVal = 2 + md := MetricDataFromCfg(cfg) + pts := getMetric(md).DoubleHistogram().DataPoints() + pt := pts.At(0) + buckets := pt.BucketCounts() + require.Equal(t, 5, len(buckets)) + require.EqualValues(t, 2, buckets[2]) +} + +func TestGenDoubleGauge(t *testing.T) { + cfg := DefaultCfg() + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleGauge + md := MetricDataFromCfg(cfg) + metric := getMetric(md) + pts := metric.DoubleGauge().DataPoints() + require.Equal(t, 1, pts.Len()) + pt := pts.At(0) + require.EqualValues(t, 1, pt.Value()) +} + +func getMetric(md pdata.Metrics) pdata.Metric { + return md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) +} diff --git a/internal/otel_collector/internal/goldendataset/pict_metric_gen.go b/internal/otel_collector/internal/goldendataset/pict_metric_gen.go new file mode 100644 index 00000000000..53b8e853563 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/pict_metric_gen.go @@ -0,0 +1,100 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "fmt" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// GenerateMetricDatas takes the filename of a PICT-generated file, walks through all of the rows in the PICT +// file and for each row, generates a MetricData object, collecting them and returning them to the caller. +func GenerateMetricDatas(metricPairsFile string) ([]pdata.Metrics, error) { + pictData, err := loadPictOutputFile(metricPairsFile) + if err != nil { + return nil, err + } + var out []pdata.Metrics + for i, values := range pictData { + if i == 0 { + continue + } + metricInputs := PICTMetricInputs{ + NumPtsPerMetric: PICTNumPtsPerMetric(values[0]), + MetricType: PICTMetricDataType(values[1]), + NumPtLabels: PICTNumPtLabels(values[2]), + } + cfg := pictToCfg(metricInputs) + cfg.MetricNamePrefix = fmt.Sprintf("pict_%d_", i) + md := MetricDataFromCfg(cfg) + out = append(out, md) + } + return out, nil +} + +func pictToCfg(inputs PICTMetricInputs) MetricCfg { + cfg := DefaultCfg() + switch inputs.NumResourceAttrs { + case AttrsNone: + cfg.NumResourceAttrs = 0 + case AttrsOne: + cfg.NumResourceAttrs = 1 + case AttrsTwo: + cfg.NumResourceAttrs = 2 + } + + switch inputs.NumPtsPerMetric { + case NumPtsPerMetricOne: + cfg.NumPtsPerMetric = 1 + case NumPtsPerMetricMany: + cfg.NumPtsPerMetric = 16 + } + + switch inputs.MetricType { + case MetricTypeIntGauge: + cfg.MetricDescriptorType = pdata.MetricDataTypeIntGauge + case MetricTypeMonotonicIntSum: + cfg.MetricDescriptorType = pdata.MetricDataTypeIntSum + cfg.IsMonotonicSum = true + case MetricTypeNonMonotonicIntSum: + cfg.MetricDescriptorType = pdata.MetricDataTypeIntSum + cfg.IsMonotonicSum = false + case MetricTypeDoubleGauge: + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleGauge + case MetricTypeMonotonicDoubleSum: + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleSum + cfg.IsMonotonicSum = true + case MetricTypeNonMonotonicDoubleSum: + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleSum + cfg.IsMonotonicSum = false + case MetricTypeIntHistogram: + cfg.MetricDescriptorType = pdata.MetricDataTypeIntHistogram + case MetricTypeDoubleHistogram: + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleHistogram + default: + panic("Should not happen, unsupported type " + string(inputs.MetricType)) + } + + switch inputs.NumPtLabels { + case LabelsNone: + cfg.NumPtLabels = 0 + case LabelsOne: + cfg.NumPtLabels = 1 + case LabelsMany: + cfg.NumPtLabels = 16 + } + return cfg +} diff --git a/internal/otel_collector/internal/goldendataset/pict_metric_gen_test.go b/internal/otel_collector/internal/goldendataset/pict_metric_gen_test.go new file mode 100644 index 00000000000..678d6237516 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/pict_metric_gen_test.go @@ -0,0 +1,93 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestGenerateMetricDatas(t *testing.T) { + mds, err := GenerateMetricDatas("testdata/generated_pict_pairs_metrics.txt") + require.NoError(t, err) + require.Equal(t, 25, len(mds)) +} + +func TestPICTtoCfg(t *testing.T) { + tests := []struct { + name string + inputs PICTMetricInputs + cfg MetricCfg + }{ + { + name: "none", + inputs: PICTMetricInputs{ + NumResourceAttrs: AttrsNone, + NumPtsPerMetric: NumPtsPerMetricOne, + MetricType: MetricTypeIntGauge, + NumPtLabels: LabelsNone, + }, + cfg: MetricCfg{ + NumResourceAttrs: 0, + NumPtsPerMetric: 1, + MetricDescriptorType: pdata.MetricDataTypeIntGauge, + NumPtLabels: 0, + }, + }, + { + name: "one", + inputs: PICTMetricInputs{ + NumResourceAttrs: AttrsOne, + NumPtsPerMetric: NumPtsPerMetricOne, + MetricType: MetricTypeDoubleGauge, + NumPtLabels: LabelsOne, + }, + cfg: MetricCfg{ + NumResourceAttrs: 1, + NumPtsPerMetric: 1, + MetricDescriptorType: pdata.MetricDataTypeDoubleGauge, + NumPtLabels: 1, + }, + }, + { + name: "many", + inputs: PICTMetricInputs{ + NumResourceAttrs: AttrsTwo, + NumPtsPerMetric: NumPtsPerMetricMany, + MetricType: MetricTypeDoubleHistogram, + NumPtLabels: LabelsMany, + }, + cfg: MetricCfg{ + NumResourceAttrs: 2, + NumPtsPerMetric: 16, + MetricDescriptorType: pdata.MetricDataTypeDoubleHistogram, + NumPtLabels: 16, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := pictToCfg(test.inputs) + expected := test.cfg + require.Equal(t, expected.NumResourceAttrs, actual.NumResourceAttrs) + require.Equal(t, expected.NumPtsPerMetric, actual.NumPtsPerMetric) + require.Equal(t, expected.MetricDescriptorType, actual.MetricDescriptorType) + require.Equal(t, expected.NumPtLabels, actual.NumPtLabels) + }) + } +} diff --git a/internal/otel_collector/internal/goldendataset/pict_metrics_input_defs.go b/internal/otel_collector/internal/goldendataset/pict_metrics_input_defs.go new file mode 100644 index 00000000000..cab11a0686b --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/pict_metrics_input_defs.go @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +// Start of PICT inputs for generating golden dataset metrics (pict_input_metrics.txt) + +// PICTMetricInputs defines one pairwise combination of MetricData variations +type PICTMetricInputs struct { + // Specifies the number of points on each metric. + NumPtsPerMetric PICTNumPtsPerMetric + // Specifies the types of metrics that can be generated. + MetricType PICTMetricDataType + // Specifies the number of labels on each datapoint. + NumPtLabels PICTNumPtLabels + // Specifies the number of attributes on each resource. + NumResourceAttrs PICTNumResourceAttrs +} + +// Enumerates the types of metrics that can be generated. +type PICTMetricDataType string + +const ( + MetricTypeIntGauge PICTMetricDataType = "IntGauge" + MetricTypeMonotonicIntSum PICTMetricDataType = "MonotonicIntSum" + MetricTypeNonMonotonicIntSum PICTMetricDataType = "NonMonotonicIntSum" + MetricTypeDoubleGauge PICTMetricDataType = "DoubleGauge" + MetricTypeMonotonicDoubleSum PICTMetricDataType = "MonotonicDoubleSum" + MetricTypeNonMonotonicDoubleSum PICTMetricDataType = "NonMonotonicDoubleSum" + MetricTypeIntHistogram PICTMetricDataType = "IntHistogram" + MetricTypeDoubleHistogram PICTMetricDataType = "DoubleHistogram" +) + +// Enumerates the number of labels on each datapoint. +type PICTNumPtLabels string + +const ( + LabelsNone PICTNumPtLabels = "NoLabels" + LabelsOne PICTNumPtLabels = "OneLabel" + LabelsMany PICTNumPtLabels = "ManyLabels" +) + +// Enumerates the number of points on each metric. +type PICTNumPtsPerMetric string + +const ( + NumPtsPerMetricOne PICTNumPtsPerMetric = "OnePt" + NumPtsPerMetricMany PICTNumPtsPerMetric = "ManyPts" +) + +// Enumerates the number of attributes on each resource. +type PICTNumResourceAttrs string + +const ( + AttrsNone PICTNumResourceAttrs = "NoAttrs" + AttrsOne PICTNumResourceAttrs = "OneAttr" + AttrsTwo PICTNumResourceAttrs = "TwoAttrs" +) diff --git a/internal/otel_collector/internal/goldendataset/pict_tracing_input_defs.go b/internal/otel_collector/internal/goldendataset/pict_tracing_input_defs.go new file mode 100644 index 00000000000..fcf1aa28700 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/pict_tracing_input_defs.go @@ -0,0 +1,170 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +// Start of PICT inputs for generating golden dataset ResourceSpans (pict_input_traces.txt) + +// Input columns in pict_input_traces.txt +const ( + TracesColumnResource = 0 + TracesColumnInstrumentationLibrary = 1 + TracesColumnSpans = 2 +) + +// Enumerates the supported types of resource instances that can be generated. +type PICTInputResource string + +const ( + ResourceNil PICTInputResource = "Nil" + ResourceEmpty PICTInputResource = "Empty" + ResourceVMOnPrem PICTInputResource = "VMOnPrem" + ResourceVMCloud PICTInputResource = "VMCloud" + ResourceK8sOnPrem PICTInputResource = "K8sOnPrem" + ResourceK8sCloud PICTInputResource = "K8sCloud" + ResourceFaas PICTInputResource = "Faas" + ResourceExec PICTInputResource = "Exec" +) + +// Enumerates the number and kind of instrumentation library instances that can be generated. +type PICTInputInstrumentationLibrary string + +const ( + LibraryNone PICTInputInstrumentationLibrary = "None" + LibraryOne PICTInputInstrumentationLibrary = "One" + LibraryTwo PICTInputInstrumentationLibrary = "Two" +) + +// Enumerates the relative sizes of tracing spans that can be attached to an instrumentation library span instance. +type PICTInputSpans string + +const ( + LibrarySpansNone PICTInputSpans = "None" + LibrarySpansOne PICTInputSpans = "One" + LibrarySpansSeveral PICTInputSpans = "Several" + LibrarySpansAll PICTInputSpans = "All" +) + +// PICTTracingInputs defines one pairwise combination of ResourceSpans variations +type PICTTracingInputs struct { + // Specifies the category of attributes to populate the Resource field with + Resource PICTInputResource + // Specifies the number and library categories to populte the InstrumentationLibrarySpans field with + InstrumentationLibrary PICTInputInstrumentationLibrary + // Specifies the relative number of spans to populate the InstrumentationLibrarySpans' Spans field with + Spans PICTInputSpans +} + +// Start of PICT inputs for generating golden dataset Spans (pict_input_spans.txt) + +// Input columns in pict_input_spans.txt +const ( + SpansColumnParent = 0 + SpansColumnTracestate = 1 + SpansColumnKind = 2 + SpansColumnAttributes = 3 + SpansColumnEvents = 4 + SpansColumnLinks = 5 + SpansColumnStatus = 6 +) + +// Enumerates the parent/child types of spans that can be generated. +type PICTInputParent string + +const ( + SpanParentRoot PICTInputParent = "Root" + SpanParentChild PICTInputParent = "Child" +) + +// Enumerates the categories of tracestate values that can be generated for a span. +type PICTInputTracestate string + +const ( + TraceStateEmpty PICTInputTracestate = "Empty" + TraceStateOne PICTInputTracestate = "One" + TraceStateFour PICTInputTracestate = "Four" +) + +// Enumerates the span kind values that can be set for a span. +type PICTInputKind string + +const ( + SpanKindUnspecified PICTInputKind = "Unspecified" + SpanKindInternal PICTInputKind = "Internal" + SpanKindServer PICTInputKind = "Server" + SpanKindClient PICTInputKind = "Client" + SpanKindProducer PICTInputKind = "Producer" + SpanKindConsumer PICTInputKind = "Consumer" +) + +// Enumerates the categories of representative attributes a generated span can be populated with. +type PICTInputAttributes string + +const ( + SpanAttrNil PICTInputAttributes = "Nil" + SpanAttrEmpty PICTInputAttributes = "Empty" + SpanAttrDatabaseSQL PICTInputAttributes = "DatabaseSQL" + SpanAttrDatabaseNoSQL PICTInputAttributes = "DatabaseNoSQL" + SpanAttrFaaSDatasource PICTInputAttributes = "FaaSDatasource" + SpanAttrFaaSHTTP PICTInputAttributes = "FaaSHTTP" + SpanAttrFaaSPubSub PICTInputAttributes = "FaaSPubSub" + SpanAttrFaaSTimer PICTInputAttributes = "FaaSTimer" + SpanAttrFaaSOther PICTInputAttributes = "FaaSOther" + SpanAttrHTTPClient PICTInputAttributes = "HTTPClient" + SpanAttrHTTPServer PICTInputAttributes = "HTTPServer" + SpanAttrMessagingProducer PICTInputAttributes = "MessagingProducer" + SpanAttrMessagingConsumer PICTInputAttributes = "MessagingConsumer" + SpanAttrGRPCClient PICTInputAttributes = "gRPCClient" + SpanAttrGRPCServer PICTInputAttributes = "gRPCServer" + SpanAttrInternal PICTInputAttributes = "Internal" + SpanAttrMaxCount PICTInputAttributes = "MaxCount" +) + +// Enumerates the categories of events and/or links a generated span can be populated with. +type PICTInputSpanChild string + +const ( + SpanChildCountNil PICTInputSpanChild = "Nil" + SpanChildCountEmpty PICTInputSpanChild = "Empty" + SpanChildCountOne PICTInputSpanChild = "One" + SpanChildCountTwo PICTInputSpanChild = "Two" + SpanChildCountEight PICTInputSpanChild = "Eight" +) + +// Enumerates the status values a generated span can be populated with. +type PICTInputStatus string + +const ( + SpanStatusUnset PICTInputStatus = "Unset" + SpanStatusOk PICTInputStatus = "Ok" + SpanStatusError PICTInputStatus = "Error" +) + +// PICTSpanInputs defines one pairwise combination of Span variations +type PICTSpanInputs struct { + // Specifies whether the ParentSpanId field should be populated or not + Parent PICTInputParent + // Specifies the category of contents to populate the TraceState field with + Tracestate PICTInputTracestate + // Specifies the value to populate the Kind field with + Kind PICTInputKind + // Specifies the category of values to populate the Attributes field with + Attributes PICTInputAttributes + // Specifies the category of contents to populate the Events field with + Events PICTInputSpanChild + // Specifies the category of contents to populate the Links field with + Links PICTInputSpanChild + // Specifies the value to populate the Status field with + Status PICTInputStatus +} diff --git a/internal/otel_collector/internal/goldendataset/resource_generator.go b/internal/otel_collector/internal/goldendataset/resource_generator.go new file mode 100644 index 00000000000..aba7483f38a --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/resource_generator.go @@ -0,0 +1,170 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" + "go.opentelemetry.io/collector/translator/conventions" +) + +// GenerateResource generates a OTLP Resource object with representative attributes for the +// underlying resource type specified by the rscID input parameter. +func GenerateResource(rscID PICTInputResource) otlpresource.Resource { + var attrs map[string]interface{} + switch rscID { + case ResourceNil: + attrs = generateNilAttributes() + case ResourceEmpty: + attrs = generateEmptyAttributes() + case ResourceVMOnPrem: + attrs = generateOnpremVMAttributes() + case ResourceVMCloud: + attrs = generateCloudVMAttributes() + case ResourceK8sOnPrem: + attrs = generateOnpremK8sAttributes() + case ResourceK8sCloud: + attrs = generateCloudK8sAttributes() + case ResourceFaas: + attrs = generateFassAttributes() + case ResourceExec: + attrs = generateExecAttributes() + default: + attrs = generateEmptyAttributes() + } + var dropped uint32 + if len(attrs) < 10 { + dropped = 0 + } else { + dropped = uint32(len(attrs) % 4) + } + return otlpresource.Resource{ + Attributes: convertMapToAttributeKeyValues(attrs), + DroppedAttributesCount: dropped, + } +} + +func generateNilAttributes() map[string]interface{} { + return nil +} + +func generateEmptyAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + return attrMap +} + +func generateOnpremVMAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeServiceName] = "customers" + attrMap[conventions.AttributeServiceNamespace] = "production" + attrMap[conventions.AttributeServiceVersion] = "semver:0.7.3" + subMap := make(map[string]interface{}) + subMap["public"] = "tc-prod9.internal.example.com" + subMap["internal"] = "172.18.36.18" + attrMap[conventions.AttributeHostName] = &otlpcommon.KeyValueList{ + Values: convertMapToAttributeKeyValues(subMap), + } + attrMap[conventions.AttributeHostImageID] = "661ADFA6-E293-4870-9EFA-1AA052C49F18" + attrMap[conventions.AttributeTelemetrySDKLanguage] = conventions.AttributeSDKLangValueJava + attrMap[conventions.AttributeTelemetrySDKName] = "opentelemetry" + attrMap[conventions.AttributeTelemetrySDKVersion] = "0.3.0" + return attrMap +} + +func generateCloudVMAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeServiceName] = "shoppingcart" + attrMap[conventions.AttributeServiceName] = "customers" + attrMap[conventions.AttributeServiceNamespace] = "production" + attrMap[conventions.AttributeServiceVersion] = "semver:0.7.3" + attrMap[conventions.AttributeTelemetrySDKLanguage] = conventions.AttributeSDKLangValueJava + attrMap[conventions.AttributeTelemetrySDKName] = "opentelemetry" + attrMap[conventions.AttributeTelemetrySDKVersion] = "0.3.0" + attrMap[conventions.AttributeHostID] = "57e8add1f79a454bae9fb1f7756a009a" + attrMap[conventions.AttributeHostName] = "env-check" + attrMap[conventions.AttributeHostImageID] = "5.3.0-1020-azure" + attrMap[conventions.AttributeHostType] = "B1ms" + attrMap[conventions.AttributeCloudProvider] = "azure" + attrMap[conventions.AttributeCloudAccount] = "2f5b8278-4b80-4930-a6bb-d86fc63a2534" + attrMap[conventions.AttributeCloudRegion] = "South Central US" + return attrMap +} + +func generateOnpremK8sAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeContainerName] = "cert-manager" + attrMap[conventions.AttributeContainerImage] = "quay.io/jetstack/cert-manager-controller:v0.14.2" + attrMap[conventions.AttributeK8sCluster] = "docker-desktop" + attrMap[conventions.AttributeK8sNamespace] = "cert-manager" + attrMap[conventions.AttributeK8sDeployment] = "cm-1-cert-manager" + attrMap[conventions.AttributeK8sPod] = "cm-1-cert-manager-6448b4949b-t2jtd" + attrMap[conventions.AttributeHostName] = "docker-desktop" + return attrMap +} + +func generateCloudK8sAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeContainerName] = "otel-collector" + attrMap[conventions.AttributeContainerImage] = "otel/opentelemetry-collector-contrib" + attrMap[conventions.AttributeContainerTag] = "0.4.0" + attrMap[conventions.AttributeK8sCluster] = "erp-dev" + attrMap[conventions.AttributeK8sNamespace] = "monitoring" + attrMap[conventions.AttributeK8sDeployment] = "otel-collector" + attrMap[conventions.AttributeK8sDeploymentUID] = "4D614B27-EDAF-409B-B631-6963D8F6FCD4" + attrMap[conventions.AttributeK8sReplicaSet] = "otel-collector-2983fd34" + attrMap[conventions.AttributeK8sReplicaSetUID] = "EC7D59EF-D5B6-48B7-881E-DA6B7DD539B6" + attrMap[conventions.AttributeK8sPod] = "otel-collector-6484db5844-c6f9m" + attrMap[conventions.AttributeK8sPodUID] = "FDFD941E-2A7A-4945-B601-88DD486161A4" + attrMap[conventions.AttributeHostID] = "ec2e3fdaffa294348bdf355156b94cda" + attrMap[conventions.AttributeHostName] = "10.99.118.157" + attrMap[conventions.AttributeHostImageID] = "ami-011c865bf7da41a9d" + attrMap[conventions.AttributeHostType] = "m5.xlarge" + attrMap[conventions.AttributeCloudProvider] = "aws" + attrMap[conventions.AttributeCloudAccount] = "12345678901" + attrMap[conventions.AttributeCloudRegion] = "us-east-1" + attrMap[conventions.AttributeCloudZone] = "us-east-1c" + return attrMap +} + +func generateFassAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeFaasID] = "https://us-central1-dist-system-demo.cloudfunctions.net/env-vars-print" + attrMap[conventions.AttributeFaasName] = "env-vars-print" + attrMap[conventions.AttributeFaasVersion] = "semver:1.0.0" + attrMap[conventions.AttributeCloudProvider] = "gcp" + attrMap[conventions.AttributeCloudAccount] = "opentelemetry" + attrMap[conventions.AttributeCloudRegion] = "us-central1" + attrMap[conventions.AttributeCloudZone] = "us-central1-a" + return attrMap +} + +func generateExecAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeProcessExecutableName] = "otelcol" + parts := make([]otlpcommon.AnyValue, 3) + parts[0] = otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "otelcol"}} + parts[1] = otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "--config=/etc/otel-collector-config.yaml"}} + parts[2] = otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "--mem-ballast-size-mib=683"}} + attrMap[conventions.AttributeProcessCommandLine] = &otlpcommon.ArrayValue{ + Values: parts, + } + attrMap[conventions.AttributeProcessExecutablePath] = "/usr/local/bin/otelcol" + attrMap[conventions.AttributeProcessID] = 2020 + attrMap[conventions.AttributeProcessOwner] = "otel" + attrMap[conventions.AttributeOSType] = "LINUX" + attrMap[conventions.AttributeOSDescription] = + "Linux ubuntu 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux" + return attrMap +} diff --git a/internal/otel_collector/internal/goldendataset/resource_generator_test.go b/internal/otel_collector/internal/goldendataset/resource_generator_test.go new file mode 100644 index 00000000000..f11b9ca0bfa --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/resource_generator_test.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" +) + +func TestGenerateResource(t *testing.T) { + resourceIds := []PICTInputResource{ResourceNil, ResourceEmpty, ResourceVMOnPrem, ResourceVMCloud, ResourceK8sOnPrem, + ResourceK8sCloud, ResourceFaas, ResourceExec} + for _, rscID := range resourceIds { + rsc := GenerateResource(rscID) + if rscID == ResourceNil { + assert.Nil(t, rsc.Attributes) + } else { + assert.NotNil(t, rsc.Attributes) + } + // test marshal/unmarshal + bytes, err := rsc.Marshal() + if err != nil { + assert.Fail(t, err.Error()) + } + if len(bytes) > 0 { + copy := &otlpresource.Resource{} + err = copy.Unmarshal(bytes) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.EqualValues(t, len(rsc.Attributes), len(copy.Attributes)) + } + } +} diff --git a/internal/otel_collector/internal/goldendataset/span_generator.go b/internal/otel_collector/internal/goldendataset/span_generator.go new file mode 100644 index 00000000000..5c2557e07ff --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/span_generator.go @@ -0,0 +1,529 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "fmt" + "io" + "time" + + "go.opentelemetry.io/collector/internal/data" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/translator/conventions" +) + +var statusCodeMap = map[PICTInputStatus]otlptrace.Status_StatusCode{ + SpanStatusUnset: otlptrace.Status_STATUS_CODE_UNSET, + SpanStatusOk: otlptrace.Status_STATUS_CODE_OK, + SpanStatusError: otlptrace.Status_STATUS_CODE_ERROR, +} + +var statusMsgMap = map[PICTInputStatus]string{ + SpanStatusUnset: "Unset", + SpanStatusOk: "Ok", + SpanStatusError: "Error", +} + +// GenerateSpans generates a slice of OTLP Span objects with the number of spans specified by the count input +// parameter. The startPos parameter specifies the line in the PICT tool-generated, test parameter +// combination records file specified by the pictFile parameter to start reading from. When the end record +// is reached it loops back to the first record. The random parameter injects the random number generator +// to use in generating IDs and other random values. Using a random number generator with the same seed value +// enables reproducible tests. +// +// The return values are the slice with the generated spans, the starting position for the next generation +// run and the error which caused the spans generation to fail. If err is not nil, the spans slice will +// have nil values. +func GenerateSpans(count int, startPos int, pictFile string, random io.Reader) ([]*otlptrace.Span, int, error) { + pairsData, err := loadPictOutputFile(pictFile) + if err != nil { + return nil, 0, err + } + pairsTotal := len(pairsData) + spanList := make([]*otlptrace.Span, count) + index := startPos + 1 + var inputs []string + var spanInputs *PICTSpanInputs + var traceID data.TraceID + var parentID data.SpanID + for i := 0; i < count; i++ { + if index >= pairsTotal { + index = 1 + } + inputs = pairsData[index] + spanInputs = &PICTSpanInputs{ + Parent: PICTInputParent(inputs[SpansColumnParent]), + Tracestate: PICTInputTracestate(inputs[SpansColumnTracestate]), + Kind: PICTInputKind(inputs[SpansColumnKind]), + Attributes: PICTInputAttributes(inputs[SpansColumnAttributes]), + Events: PICTInputSpanChild(inputs[SpansColumnEvents]), + Links: PICTInputSpanChild(inputs[SpansColumnLinks]), + Status: PICTInputStatus(inputs[SpansColumnStatus]), + } + switch spanInputs.Parent { + case SpanParentRoot: + traceID = generateTraceID(random) + parentID = data.NewSpanID([8]byte{}) + case SpanParentChild: + // use existing if available + if !traceID.IsValid() { + traceID = generateTraceID(random) + } + if !parentID.IsValid() { + parentID = generateSpanID(random) + } + } + spanName := generateSpanName(spanInputs) + spanList[i] = GenerateSpan(traceID, parentID, spanName, spanInputs, random) + parentID = spanList[i].SpanId + index++ + } + return spanList, index, nil +} + +func generateSpanName(spanInputs *PICTSpanInputs) string { + return fmt.Sprintf("/%s/%s/%s/%s/%s/%s/%s", spanInputs.Parent, spanInputs.Tracestate, spanInputs.Kind, + spanInputs.Attributes, spanInputs.Events, spanInputs.Links, spanInputs.Status) +} + +// GenerateSpan generates a single OTLP Span based on the input values provided. They are: +// traceID - the trace ID to use, should not be nil +// parentID - the parent span ID or nil if it is a root span +// spanName - the span name, should not be blank +// spanInputs - the pairwise combination of field value variations for this span +// random - the random number generator to use in generating ID values +// +// The generated span is returned. +func GenerateSpan(traceID data.TraceID, parentID data.SpanID, spanName string, spanInputs *PICTSpanInputs, + random io.Reader) *otlptrace.Span { + endTime := time.Now().Add(-50 * time.Microsecond) + return &otlptrace.Span{ + TraceId: traceID, + SpanId: generateSpanID(random), + TraceState: generateTraceState(spanInputs.Tracestate), + ParentSpanId: parentID, + Name: spanName, + Kind: lookupSpanKind(spanInputs.Kind), + StartTimeUnixNano: uint64(endTime.Add(-215 * time.Millisecond).UnixNano()), + EndTimeUnixNano: uint64(endTime.UnixNano()), + Attributes: generateSpanAttributes(spanInputs.Attributes, spanInputs.Status), + DroppedAttributesCount: 0, + Events: generateSpanEvents(spanInputs.Events), + DroppedEventsCount: 0, + Links: generateSpanLinks(spanInputs.Links, random), + DroppedLinksCount: 0, + Status: generateStatus(spanInputs.Status), + } +} + +func generateTraceState(tracestate PICTInputTracestate) string { + switch tracestate { + case TraceStateOne: + return "lasterror=f39cd56cc44274fd5abd07ef1164246d10ce2955" + case TraceStateFour: + return "err@ck=80ee5638,rate@ck=1.62,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" + case TraceStateEmpty: + fallthrough + default: + return "" + } +} + +func lookupSpanKind(kind PICTInputKind) otlptrace.Span_SpanKind { + switch kind { + case SpanKindClient: + return otlptrace.Span_SPAN_KIND_CLIENT + case SpanKindServer: + return otlptrace.Span_SPAN_KIND_SERVER + case SpanKindProducer: + return otlptrace.Span_SPAN_KIND_PRODUCER + case SpanKindConsumer: + return otlptrace.Span_SPAN_KIND_CONSUMER + case SpanKindInternal: + return otlptrace.Span_SPAN_KIND_INTERNAL + case SpanKindUnspecified: + fallthrough + default: + return otlptrace.Span_SPAN_KIND_UNSPECIFIED + } +} + +func generateSpanAttributes(spanTypeID PICTInputAttributes, statusStr PICTInputStatus) []otlpcommon.KeyValue { + includeStatus := SpanStatusUnset != statusStr + var attrs map[string]interface{} + switch spanTypeID { + case SpanAttrNil: + attrs = nil + case SpanAttrEmpty: + attrs = make(map[string]interface{}) + case SpanAttrDatabaseSQL: + attrs = generateDatabaseSQLAttributes() + case SpanAttrDatabaseNoSQL: + attrs = generateDatabaseNoSQLAttributes() + case SpanAttrFaaSDatasource: + attrs = generateFaaSDatasourceAttributes() + case SpanAttrFaaSHTTP: + attrs = generateFaaSHTTPAttributes(includeStatus) + case SpanAttrFaaSPubSub: + attrs = generateFaaSPubSubAttributes() + case SpanAttrFaaSTimer: + attrs = generateFaaSTimerAttributes() + case SpanAttrFaaSOther: + attrs = generateFaaSOtherAttributes() + case SpanAttrHTTPClient: + attrs = generateHTTPClientAttributes(includeStatus) + case SpanAttrHTTPServer: + attrs = generateHTTPServerAttributes(includeStatus) + case SpanAttrMessagingProducer: + attrs = generateMessagingProducerAttributes() + case SpanAttrMessagingConsumer: + attrs = generateMessagingConsumerAttributes() + case SpanAttrGRPCClient: + attrs = generateGRPCClientAttributes() + case SpanAttrGRPCServer: + attrs = generateGRPCServerAttributes() + case SpanAttrInternal: + attrs = generateInternalAttributes() + case SpanAttrMaxCount: + attrs = generateMaxCountAttributes(includeStatus) + default: + attrs = generateGRPCClientAttributes() + } + return convertMapToAttributeKeyValues(attrs) +} + +func generateStatus(statusStr PICTInputStatus) otlptrace.Status { + if SpanStatusUnset == statusStr { + return otlptrace.Status{} + } + return otlptrace.Status{ + Code: statusCodeMap[statusStr], + Message: statusMsgMap[statusStr], + } +} + +func generateDatabaseSQLAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeDBSystem] = "mysql" + attrMap[conventions.AttributeDBConnectionString] = "Server=shopdb.example.com;Database=ShopDb;Uid=billing_user;TableCache=true;UseCompression=True;MinimumPoolSize=10;MaximumPoolSize=50;" + attrMap[conventions.AttributeDBUser] = "billing_user" + attrMap[conventions.AttributeNetHostIP] = "192.0.3.122" + attrMap[conventions.AttributeNetHostPort] = int64(51306) + attrMap[conventions.AttributeNetPeerName] = "shopdb.example.com" + attrMap[conventions.AttributeNetPeerIP] = "192.0.2.12" + attrMap[conventions.AttributeNetPeerPort] = int64(3306) + attrMap[conventions.AttributeNetTransport] = "IP.TCP" + attrMap[conventions.AttributeDBName] = "shopdb" + attrMap[conventions.AttributeDBStatement] = "SELECT * FROM orders WHERE order_id = 'o4711'" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateDatabaseNoSQLAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeDBSystem] = "mongodb" + attrMap[conventions.AttributeDBUser] = "the_user" + attrMap[conventions.AttributeNetPeerName] = "mongodb0.example.com" + attrMap[conventions.AttributeNetPeerIP] = "192.0.2.14" + attrMap[conventions.AttributeNetPeerPort] = int64(27017) + attrMap[conventions.AttributeNetTransport] = "IP.TCP" + attrMap[conventions.AttributeDBName] = "shopDb" + attrMap[conventions.AttributeDBOperation] = "findAndModify" + attrMap[conventions.AttributeDBMongoDBCollection] = "products" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateFaaSDatasourceAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeFaaSTrigger] = conventions.FaaSTriggerDataSource + attrMap[conventions.AttributeFaaSExecution] = "DB85AF51-5E13-473D-8454-1E2D59415EAB" + attrMap[conventions.AttributeFaaSDocumentCollection] = "faa-flight-delay-information-incoming" + attrMap[conventions.AttributeFaaSDocumentOperation] = "insert" + attrMap[conventions.AttributeFaaSDocumentTime] = "2020-05-09T19:50:06Z" + attrMap[conventions.AttributeFaaSDocumentName] = "delays-20200509-13.csv" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateFaaSHTTPAttributes(includeStatus bool) map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeFaaSTrigger] = conventions.FaaSTriggerHTTP + attrMap[conventions.AttributeHTTPMethod] = "POST" + attrMap[conventions.AttributeHTTPScheme] = "https" + attrMap[conventions.AttributeHTTPHost] = "api.opentelemetry.io" + attrMap[conventions.AttributeHTTPTarget] = "/blog/posts" + attrMap[conventions.AttributeHTTPFlavor] = "2" + if includeStatus { + attrMap[conventions.AttributeHTTPStatusCode] = int64(201) + } + attrMap[conventions.AttributeHTTPUserAgent] = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateFaaSPubSubAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeFaaSTrigger] = conventions.FaaSTriggerPubSub + attrMap[conventions.AttributeMessagingSystem] = "sqs" + attrMap[conventions.AttributeMessagingDestination] = "video-views-au" + attrMap[conventions.AttributeMessagingOperation] = "process" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateFaaSTimerAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeFaaSTrigger] = conventions.FaaSTriggerTimer + attrMap[conventions.AttributeFaaSExecution] = "73103A4C-E22F-4493-BDE8-EAE5CAB37B50" + attrMap[conventions.AttributeFaaSTime] = "2020-05-09T20:00:08Z" + attrMap[conventions.AttributeFaaSCron] = "0/15 * * * *" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateFaaSOtherAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeFaaSTrigger] = conventions.FaaSTriggerOther + attrMap["processed.count"] = int64(256) + attrMap["processed.data"] = 14.46 + attrMap["processed.errors"] = false + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateHTTPClientAttributes(includeStatus bool) map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeHTTPMethod] = "GET" + attrMap[conventions.AttributeHTTPURL] = "https://opentelemetry.io/registry/" + if includeStatus { + attrMap[conventions.AttributeHTTPStatusCode] = int64(200) + attrMap[conventions.AttributeHTTPStatusText] = "More Than OK" + } + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateHTTPServerAttributes(includeStatus bool) map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeHTTPMethod] = "POST" + attrMap[conventions.AttributeHTTPScheme] = "https" + attrMap[conventions.AttributeHTTPServerName] = "api22.opentelemetry.io" + attrMap[conventions.AttributeNetHostPort] = int64(443) + attrMap[conventions.AttributeHTTPTarget] = "/blog/posts" + attrMap[conventions.AttributeHTTPFlavor] = "2" + if includeStatus { + attrMap[conventions.AttributeHTTPStatusCode] = int64(201) + } + attrMap[conventions.AttributeHTTPUserAgent] = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36" + attrMap[conventions.AttributeHTTPRoute] = "/blog/posts" + attrMap[conventions.AttributeHTTPClientIP] = "2001:506:71f0:16e::1" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateMessagingProducerAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeMessagingSystem] = "nats" + attrMap[conventions.AttributeMessagingDestination] = "time.us.east.atlanta" + attrMap[conventions.AttributeMessagingDestinationKind] = "topic" + attrMap[conventions.AttributeMessagingMessageID] = "AA7C5438-D93A-43C8-9961-55613204648F" + attrMap["messaging.sequence"] = int64(1) + attrMap[conventions.AttributeNetPeerIP] = "10.10.212.33" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateMessagingConsumerAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeMessagingSystem] = "kafka" + attrMap[conventions.AttributeMessagingDestination] = "infrastructure-events-zone1" + attrMap[conventions.AttributeMessagingOperation] = "receive" + attrMap[conventions.AttributeNetPeerIP] = "2600:1700:1f00:11c0:4de0:c223:a800:4e87" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateGRPCClientAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeRPCService] = "PullRequestsService" + attrMap[conventions.AttributeNetPeerIP] = "2600:1700:1f00:11c0:4de0:c223:a800:4e87" + attrMap[conventions.AttributeNetHostPort] = int64(8443) + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateGRPCServerAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeRPCService] = "PullRequestsService" + attrMap[conventions.AttributeNetPeerIP] = "192.168.1.70" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateInternalAttributes() map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap["parameters"] = "account=7310,amount=1817.10" + attrMap[conventions.AttributeEnduserID] = "unittest" + return attrMap +} + +func generateMaxCountAttributes(includeStatus bool) map[string]interface{} { + attrMap := make(map[string]interface{}) + attrMap[conventions.AttributeHTTPMethod] = "POST" + attrMap[conventions.AttributeHTTPScheme] = "https" + attrMap[conventions.AttributeHTTPHost] = "api.opentelemetry.io" + attrMap[conventions.AttributeNetHostName] = "api22.opentelemetry.io" + attrMap[conventions.AttributeNetHostIP] = "2600:1700:1f00:11c0:1ced:afa5:fd88:9d48" + attrMap[conventions.AttributeNetHostPort] = int64(443) + attrMap[conventions.AttributeHTTPTarget] = "/blog/posts" + attrMap[conventions.AttributeHTTPFlavor] = "2" + if includeStatus { + attrMap[conventions.AttributeHTTPStatusCode] = int64(201) + attrMap[conventions.AttributeHTTPStatusText] = "Created" + } + attrMap[conventions.AttributeHTTPUserAgent] = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36" + attrMap[conventions.AttributeHTTPRoute] = "/blog/posts" + attrMap[conventions.AttributeHTTPClientIP] = "2600:1700:1f00:11c0:1ced:afa5:fd77:9d01" + attrMap[conventions.AttributePeerService] = "IdentifyImageService" + attrMap[conventions.AttributeNetPeerIP] = "2600:1700:1f00:11c0:1ced:afa5:fd77:9ddc" + attrMap[conventions.AttributeNetPeerPort] = int64(39111) + attrMap["ai-sampler.weight"] = 0.07 + attrMap["ai-sampler.absolute"] = false + attrMap["ai-sampler.maxhops"] = int64(6) + attrMap["application.create.location"] = "https://api.opentelemetry.io/blog/posts/806673B9-4F4D-4284-9635-3A3E3E3805BE" + stages := make([]otlpcommon.AnyValue, 3) + stages[0] = otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "Launch"}} + stages[1] = otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "Injestion"}} + stages[2] = otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "Validation"}} + attrMap["application.stages"] = &otlpcommon.ArrayValue{ + Values: stages, + } + subMap := make(map[string]interface{}) + subMap["UIx"] = false + subMap["UI4"] = true + subMap["flow-alt3"] = false + attrMap["application.abflags"] = &otlpcommon.KeyValueList{ + Values: convertMapToAttributeKeyValues(subMap), + } + attrMap["application.thread"] = "proc-pool-14" + attrMap["application.session"] = "" + attrMap["application.persist.size"] = int64(1172184) + attrMap["application.queue.size"] = int64(0) + attrMap["application.job.id"] = "0E38800B-9C4C-484E-8F2B-C7864D854321" + attrMap["application.service.sla"] = 0.34 + attrMap["application.service.slo"] = 0.55 + attrMap[conventions.AttributeEnduserID] = "unittest" + attrMap[conventions.AttributeEnduserRole] = "poweruser" + attrMap[conventions.AttributeEnduserScope] = "email profile administrator" + return attrMap +} + +func generateSpanEvents(eventCnt PICTInputSpanChild) []*otlptrace.Span_Event { + if SpanChildCountNil == eventCnt { + return nil + } + listSize := calculateListSize(eventCnt) + eventList := make([]*otlptrace.Span_Event, listSize) + for i := 0; i < listSize; i++ { + eventList[i] = generateSpanEvent(i) + } + return eventList +} + +func generateSpanLinks(linkCnt PICTInputSpanChild, random io.Reader) []*otlptrace.Span_Link { + if SpanChildCountNil == linkCnt { + return nil + } + listSize := calculateListSize(linkCnt) + linkList := make([]*otlptrace.Span_Link, listSize) + for i := 0; i < listSize; i++ { + linkList[i] = generateSpanLink(random, i) + } + return linkList +} + +func calculateListSize(listCnt PICTInputSpanChild) int { + switch listCnt { + case SpanChildCountOne: + return 1 + case SpanChildCountTwo: + return 2 + case SpanChildCountEight: + return 8 + case SpanChildCountEmpty: + fallthrough + default: + return 0 + } +} + +func generateSpanEvent(index int) *otlptrace.Span_Event { + t := time.Now().Add(-75 * time.Microsecond) + return &otlptrace.Span_Event{ + TimeUnixNano: uint64(t.UnixNano()), + Name: "message", + Attributes: generateEventAttributes(index), + DroppedAttributesCount: 0, + } +} + +func generateEventAttributes(index int) []otlpcommon.KeyValue { + if index%4 == 2 { + return nil + } + attrMap := make(map[string]interface{}) + if index%2 == 0 { + attrMap[conventions.AttributeMessageType] = "SENT" + } else { + attrMap[conventions.AttributeMessageType] = "RECEIVED" + } + attrMap[conventions.AttributeMessageID] = int64(index) + attrMap[conventions.AttributeMessageCompressedSize] = int64(17 * index) + attrMap[conventions.AttributeMessageUncompressedSize] = int64(24 * index) + if index%4 == 1 { + attrMap["app.inretry"] = true + attrMap["app.progress"] = 0.6 + attrMap["app.statemap"] = "14|5|202" + } + return convertMapToAttributeKeyValues(attrMap) +} + +func generateSpanLink(random io.Reader, index int) *otlptrace.Span_Link { + return &otlptrace.Span_Link{ + TraceId: generateTraceID(random), + SpanId: generateSpanID(random), + TraceState: "", + Attributes: generateLinkAttributes(index), + DroppedAttributesCount: 0, + } +} + +func generateLinkAttributes(index int) []otlpcommon.KeyValue { + if index%4 == 2 { + return nil + } + attrMap := generateMessagingConsumerAttributes() + if index%4 == 1 { + attrMap["app.inretry"] = true + attrMap["app.progress"] = 0.6 + attrMap["app.statemap"] = "14|5|202" + } + return convertMapToAttributeKeyValues(attrMap) +} diff --git a/internal/otel_collector/internal/goldendataset/span_generator_test.go b/internal/otel_collector/internal/goldendataset/span_generator_test.go new file mode 100644 index 00000000000..f7ebcdce642 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/span_generator_test.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/internal/data" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +func TestGenerateParentSpan(t *testing.T) { + random := rand.Reader + traceID := generateTraceID(random) + spanInputs := &PICTSpanInputs{ + Parent: SpanParentRoot, + Tracestate: TraceStateEmpty, + Kind: SpanKindServer, + Attributes: SpanAttrHTTPServer, + Events: SpanChildCountTwo, + Links: SpanChildCountOne, + Status: SpanStatusOk, + } + span := GenerateSpan(traceID, data.NewSpanID([8]byte{}), "/gotest-parent", spanInputs, random) + assert.Equal(t, traceID, span.TraceId) + assert.False(t, span.ParentSpanId.IsValid()) + assert.Equal(t, 11, len(span.Attributes)) + assert.Equal(t, otlptrace.Status_STATUS_CODE_OK, span.Status.Code) +} + +func TestGenerateChildSpan(t *testing.T) { + random := rand.Reader + traceID := generateTraceID(random) + parentID := generateSpanID(random) + spanInputs := &PICTSpanInputs{ + Parent: SpanParentChild, + Tracestate: TraceStateEmpty, + Kind: SpanKindClient, + Attributes: SpanAttrDatabaseSQL, + Events: SpanChildCountEmpty, + Links: SpanChildCountNil, + Status: SpanStatusOk, + } + span := GenerateSpan(traceID, parentID, "get_test_info", spanInputs, random) + assert.Equal(t, traceID, span.TraceId) + assert.Equal(t, parentID, span.ParentSpanId) + assert.Equal(t, 12, len(span.Attributes)) + assert.Equal(t, otlptrace.Status_STATUS_CODE_OK, span.Status.Code) +} + +func TestGenerateSpans(t *testing.T) { + random := rand.Reader + count1 := 16 + spans, nextPos, err := GenerateSpans(count1, 0, "testdata/generated_pict_pairs_spans.txt", random) + assert.Nil(t, err) + assert.Equal(t, count1, len(spans)) + count2 := 256 + spans, nextPos, err = GenerateSpans(count2, nextPos, "testdata/generated_pict_pairs_spans.txt", random) + assert.Nil(t, err) + assert.Equal(t, count2, len(spans)) + count3 := 118 + spans, _, err = GenerateSpans(count3, nextPos, "testdata/generated_pict_pairs_spans.txt", random) + assert.Nil(t, err) + assert.Equal(t, count3, len(spans)) +} diff --git a/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_metrics.txt b/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_metrics.txt new file mode 100644 index 00000000000..22395eb4e6d --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_metrics.txt @@ -0,0 +1,26 @@ +NumPtsPerMetric MetricType NumLabels NumResourceAttrs +OnePt IntGauge NoLabels NoAttrs +ManyPts NonMonotonicDoubleSum OneLabel OneAttr +OnePt IntHistogram ManyLabels OneAttr +ManyPts NonMonotonicDoubleSum NoLabels TwoAttrs +OnePt IntGauge OneLabel TwoAttrs +ManyPts NonMonotonicDoubleSum ManyLabels NoAttrs +OnePt DoubleGauge NoLabels OneAttr +ManyPts IntGauge ManyLabels OneAttr +ManyPts NonMonotonicIntSum ManyLabels TwoAttrs +ManyPts IntHistogram OneLabel NoAttrs +ManyPts MonotonicIntSum ManyLabels NoAttrs +OnePt DoubleHistogram OneLabel OneAttr +OnePt NonMonotonicIntSum NoLabels NoAttrs +ManyPts DoubleHistogram NoLabels TwoAttrs +ManyPts MonotonicDoubleSum OneLabel OneAttr +ManyPts DoubleGauge ManyLabels NoAttrs +OnePt MonotonicIntSum NoLabels TwoAttrs +OnePt IntHistogram NoLabels TwoAttrs +OnePt MonotonicDoubleSum ManyLabels NoAttrs +ManyPts DoubleGauge OneLabel TwoAttrs +ManyPts MonotonicIntSum OneLabel OneAttr +OnePt DoubleHistogram ManyLabels NoAttrs +OnePt NonMonotonicIntSum OneLabel OneAttr +OnePt NonMonotonicDoubleSum ManyLabels NoAttrs +ManyPts MonotonicDoubleSum NoLabels TwoAttrs diff --git a/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_spans.txt b/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_spans.txt new file mode 100644 index 00000000000..61d1bae4452 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_spans.txt @@ -0,0 +1,307 @@ +Parent Tracestate Kind Attributes Events Links Status +Child One Consumer FaaSDatasource Empty Nil AlreadyExists +Child Empty Unspecified gRPCClient Two One ResourceExhausted +Child Four Client gRPCClient Eight Eight DataLoss +Root Four Server FaaSHTTP One Empty ResourceExhausted +Child One Server FaaSOther Nil Two Unimplemented +Child One Unspecified HTTPClient Nil Eight InternalError +Root One Producer FaaSPubSub Two Empty Cancelled +Child One Client DatabaseSQL One One PermissionDenied +Child Four Unspecified FaaSTimer Empty Two FailedPrecondition +Child Empty Unspecified MessagingConsumer Eight Nil InvalidArgument +Root Empty Server FaaSTimer Two Eight AlreadyExists +Child One Internal Nil Eight Two ResourceExhausted +Child Empty Unspecified FaaSHTTP Nil Nil DeadlineExceeded +Child Empty Producer MessagingProducer Empty Empty Ok +Root Four Server HTTPServer Nil One Ok +Root Four Producer Empty One Nil OutOfRange +Child Empty Consumer FaaSDatasource One Two Unavailable +Child One Client gRPCClient Nil Empty OutOfRange +Child Empty Internal Internal One Eight FailedPrecondition +Root Empty Server FaaSTimer Eight One OutOfRange +Child Four Consumer FaaSDatasource Empty One Unauthenticated +Child Empty Client HTTPClient Two Nil DataLoss +Child Empty Unspecified FaaSPubSub Empty Two UnknownError +Child Four Client gRPCClient Two Two Ok +Child Four Unspecified HTTPClient Eight Empty Ok +Root One Server FaaSHTTP Empty Eight Aborted +Child One Client DatabaseNoSQL Nil One FailedPrecondition +Child Empty Client HTTPClient Empty One ResourceExhausted +Child Four Internal Nil Nil Empty AlreadyExists +Root Four Producer FaaSPubSub Eight One AlreadyExists +Child Four Client HTTPClient One Two InvalidArgument +Root One Server FaaSTimer Nil Nil UnknownError +Child Empty Unspecified HTTPServer One Two Cancelled +Child Four Server FaaSHTTP Eight One Cancelled +Child Empty Server FaaSTimer Nil Eight ResourceExhausted +Root One Server gRPCServer Nil Eight InvalidArgument +Child Four Unspecified gRPCServer Two Two Nil +Child One Consumer MessagingConsumer Nil Eight ResourceExhausted +Child One Unspecified MessagingProducer Two Nil FailedPrecondition +Child Four Consumer MessagingConsumer Two Empty Unavailable +Child One Producer FaaSPubSub One Nil Ok +Root Four Server MaxCount Empty Nil Cancelled +Root One Server HTTPServer Empty Eight DeadlineExceeded +Child One Consumer MessagingConsumer Empty Two FailedPrecondition +Child Empty Unspecified MaxCount Two One InvalidArgument +Child One Unspecified FaaSHTTP Two Two OutOfRange +Child Four Unspecified DatabaseSQL Eight Two Aborted +Child One Unspecified MaxCount Eight Eight UnknownError +Child Four Unspecified FaaSOther Eight Empty FailedPrecondition +Root One Server HTTPServer Eight Nil Unavailable +Root Empty Server MaxCount Nil Two Ok +Child Empty Consumer MessagingConsumer One One Aborted +Child One Client Empty Eight One Nil +Root Four Producer MessagingProducer Eight Eight PermissionDenied +Child Empty Internal Nil Empty Nil Nil +Child Empty Unspecified DatabaseNoSQL Two Two NotFound +Child Empty Client DatabaseSQL Nil Empty Nil +Child Four Producer FaaSPubSub Nil Eight ResourceExhausted +Child Empty Unspecified FaaSOther Two One DeadlineExceeded +Child Four Consumer FaaSDatasource Eight Empty InternalError +Root Empty Producer Empty Two Two ResourceExhausted +Root Four Server FaaSOther One Eight Nil +Child Four Internal Internal Two Nil PermissionDenied +Child One Client DatabaseSQL Empty Eight FailedPrecondition +Child Four Producer MessagingProducer One One InvalidArgument +Child Four Unspecified DatabaseNoSQL Empty Empty InvalidArgument +Child Four Unspecified DatabaseNoSQL One Nil ResourceExhausted +Child Empty Producer MessagingProducer Nil Nil Aborted +Child Empty Server gRPCServer Empty Empty Aborted +Child One Unspecified DatabaseNoSQL Eight One DataLoss +Root One Producer MessagingProducer Nil Two DataLoss +Root Four Producer FaaSPubSub Empty One FailedPrecondition +Child Four Client DatabaseNoSQL Empty Eight Unavailable +Child Four Consumer Nil One One NotFound +Root One Server Nil Two Eight DataLoss +Child Four Internal Internal Nil One UnknownError +Child One Producer FaaSPubSub Nil One Unavailable +Child Four Client DatabaseNoSQL Two Empty Unimplemented +Child One Unspecified FaaSOther Empty Empty UnknownError +Child One Client gRPCClient Empty Nil Nil +Child One Unspecified Internal Eight Two Nil +Child Four Unspecified FaaSDatasource Two Eight Ok +Child One Unspecified Empty Nil Empty Ok +Child One Consumer FaaSDatasource Empty Eight OutOfRange +Child Empty Consumer MessagingConsumer Eight One Unimplemented +Child Empty Unspecified Nil One Eight Unimplemented +Child Four Client gRPCClient One Nil Unimplemented +Child Empty Unspecified DatabaseSQL Two Nil Ok +Child One Client DatabaseNoSQL Nil Eight Unauthenticated +Child Four Internal Internal One Empty DeadlineExceeded +Child One Unspecified gRPCServer One Nil OutOfRange +Child Empty Unspecified MaxCount One Two AlreadyExists +Root Empty Server FaaSOther Nil Empty PermissionDenied +Child Four Internal Internal Empty Two InvalidArgument +Root Four Producer MessagingProducer Eight Two DeadlineExceeded +Root One Server FaaSOther Eight Nil NotFound +Child Empty Unspecified Nil Two One Unavailable +Child Four Internal Internal Nil Eight Ok +Child Four Producer Empty Empty Eight FailedPrecondition +Child One Server gRPCServer Eight One DeadlineExceeded +Child Four Consumer MessagingConsumer Two Nil Nil +Root Four Server gRPCServer Eight Two FailedPrecondition +Root Four Producer Empty Empty Nil Unavailable +Root Empty Server HTTPServer Two Empty Unauthenticated +Child Empty Unspecified FaaSHTTP One Empty DataLoss +Child Four Client DatabaseNoSQL One Nil DeadlineExceeded +Root One Producer FaaSPubSub Empty Nil Unimplemented +Root Empty Producer MessagingProducer One One InternalError +Child Empty Unspecified FaaSOther Two Empty AlreadyExists +Child Empty Unspecified DatabaseSQL Empty Nil ResourceExhausted +Child Four Unspecified gRPCClient Eight Nil Unauthenticated +Child Four Client HTTPClient Two Nil UnknownError +Child Four Unspecified HTTPServer Empty Two PermissionDenied +Root Four Producer MessagingProducer One Two AlreadyExists +Child One Unspecified HTTPClient Eight Two PermissionDenied +Child Four Consumer Nil Nil Two Ok +Child Empty Internal Internal Nil Empty NotFound +Child Four Unspecified FaaSDatasource Nil Two FailedPrecondition +Root One Server MaxCount Empty Empty InternalError +Child One Consumer Nil One Eight InvalidArgument +Child One Unspecified HTTPClient Empty Nil OutOfRange +Child Four Client HTTPClient Empty One DeadlineExceeded +Child Empty Client DatabaseSQL Nil Eight Cancelled +Child Four Internal Internal Nil Two Cancelled +Child Four Consumer MessagingConsumer Two Nil InternalError +Child Empty Consumer MessagingConsumer Eight Nil OutOfRange +Root Four Producer MessagingProducer Empty Two Unimplemented +Root One Server FaaSTimer One Empty InvalidArgument +Child Empty Client Empty Empty Eight NotFound +Child Four Unspecified FaaSOther Two Two InternalError +Child One Client DatabaseNoSQL One One Ok +Child One Unspecified MessagingConsumer One Empty Ok +Child Four Unspecified FaaSHTTP Two Empty NotFound +Root Empty Server FaaSTimer Empty Eight Unimplemented +Child One Unspecified FaaSPubSub Nil Nil PermissionDenied +Root Empty Server HTTPServer Eight Two InvalidArgument +Child Four Client HTTPClient One Two Unauthenticated +Child Empty Server gRPCServer One Nil InternalError +Root Empty Producer MessagingProducer Empty Eight OutOfRange +Child Four Producer MessagingProducer Eight Nil Nil +Child Empty Consumer FaaSDatasource Eight Empty Unimplemented +Child Empty Unspecified FaaSPubSub Empty Eight DataLoss +Child Four Unspecified MessagingConsumer Empty Empty AlreadyExists +Child Empty Producer FaaSPubSub One One NotFound +Child One Internal Internal Two Nil InternalError +Root Four Server FaaSTimer Nil One NotFound +Child Four Unspecified FaaSOther Nil One Unavailable +Child Empty Unspecified FaaSHTTP One Nil InternalError +Child Empty Unspecified gRPCServer Eight Nil AlreadyExists +Child One Client HTTPClient Nil One Unimplemented +Child One Client HTTPClient Empty Eight NotFound +Child Four Consumer FaaSDatasource One Eight UnknownError +Root Empty Producer MessagingProducer Two Two Unauthenticated +Child Empty Unspecified FaaSDatasource Two One Aborted +Child One Consumer MessagingConsumer Empty Nil DataLoss +Child One Consumer MessagingConsumer Eight One Cancelled +Child Empty Unspecified FaaSDatasource One Two DataLoss +Child Empty Client gRPCClient Empty Eight FailedPrecondition +Child Empty Unspecified Internal Eight Two ResourceExhausted +Child Empty Client gRPCClient One Nil InternalError +Child Empty Consumer Nil Two Nil PermissionDenied +Child Empty Producer FaaSPubSub One Eight OutOfRange +Child One Unspecified gRPCServer One Nil Ok +Child One Consumer FaaSDatasource One Empty DeadlineExceeded +Child One Unspecified FaaSDatasource Nil Eight NotFound +Child Empty Unspecified DatabaseNoSQL Empty Two PermissionDenied +Child One Unspecified FaaSHTTP Empty Empty UnknownError +Child Empty Server HTTPServer Empty One Aborted +Child Empty Unspecified HTTPClient Eight Eight Cancelled +Child Four Producer MessagingProducer One Empty Cancelled +Child Four Server MaxCount One Eight FailedPrecondition +Child Empty Internal Nil One Eight OutOfRange +Child One Unspecified gRPCServer Empty Two Cancelled +Child Four Server HTTPServer Nil Empty AlreadyExists +Child Four Unspecified Empty Two Two InvalidArgument +Root Empty Server HTTPServer Eight Two DataLoss +Child Empty Client gRPCClient Two Two Unavailable +Child Four Unspecified HTTPServer One One Nil +Child One Client gRPCClient Nil Eight DeadlineExceeded +Root One Server FaaSTimer Empty Eight Cancelled +Child Empty Consumer Nil Eight Eight Cancelled +Child Four Server FaaSTimer Eight Nil Ok +Root One Producer Empty Eight Empty UnknownError +Child One Client Empty Eight Nil AlreadyExists +Child Empty Internal Nil Eight Nil Unauthenticated +Child One Internal Nil Nil Eight DeadlineExceeded +Child One Producer Empty Two Two Cancelled +Child One Unspecified FaaSHTTP Eight Nil InvalidArgument +Child Empty Unspecified HTTPClient One One FailedPrecondition +Child One Unspecified HTTPServer Nil Empty ResourceExhausted +Child One Server Nil One Eight InternalError +Child Four Unspecified Empty Eight Nil Unauthenticated +Child Empty Unspecified MessagingConsumer Eight Two NotFound +Child Four Unspecified MaxCount Empty Eight NotFound +Child One Client gRPCClient One Two InvalidArgument +Child Four Unspecified DatabaseSQL Nil Empty InvalidArgument +Child Four Unspecified FaaSOther One Two OutOfRange +Child Empty Unspecified HTTPServer Two Nil FailedPrecondition +Child Empty Consumer FaaSDatasource Two Eight Nil +Child One Server FaaSTimer Nil One Aborted +Child Four Unspecified DatabaseNoSQL Two Empty UnknownError +Child Empty Server MaxCount Nil Nil OutOfRange +Child Four Unspecified FaaSTimer Nil Nil Unavailable +Child One Unspecified FaaSHTTP Eight Eight AlreadyExists +Child Empty Client DatabaseSQL Empty Eight UnknownError +Child One Producer Empty Eight Nil DeadlineExceeded +Child Empty Producer FaaSPubSub Empty One InternalError +Child Empty Unspecified gRPCClient Two One PermissionDenied +Child One Unspecified DatabaseSQL One Eight Unauthenticated +Child Four Client gRPCClient One Empty Cancelled +Child One Server MaxCount Empty Two Unimplemented +Child Empty Server Nil One Eight UnknownError +Root One Server gRPCServer Eight Eight DataLoss +Child Four Unspecified FaaSPubSub Two One Nil +Root One Server gRPCServer Nil Eight Unimplemented +Child One Server FaaSTimer Two Two Nil +Child Four Unspecified gRPCServer Two Eight Unauthenticated +Child Empty Server FaaSOther One Eight Unauthenticated +Child One Unspecified FaaSDatasource One Eight PermissionDenied +Child Empty Server Nil Two Two FailedPrecondition +Child One Unspecified Empty One Nil PermissionDenied +Child Four Internal Internal One Two Unimplemented +Child Empty Unspecified Empty Eight Two DataLoss +Child Empty Unspecified FaaSTimer Two Empty DeadlineExceeded +Child Empty Unspecified FaaSOther One Eight Aborted +Child One Unspecified FaaSOther One Nil ResourceExhausted +Child Empty Unspecified gRPCServer Two Nil PermissionDenied +Child Empty Unspecified MaxCount Eight Eight Aborted +Child One Consumer MessagingConsumer Two Nil Unauthenticated +Child Four Client Empty One One Unimplemented +Child Four Server MaxCount Two Eight PermissionDenied +Child One Unspecified FaaSDatasource Nil Nil ResourceExhausted +Child Empty Unspecified gRPCServer Eight Empty Unavailable +Child One Unspecified HTTPServer Nil One UnknownError +Child Four Internal Internal Nil Eight OutOfRange +Child One Unspecified FaaSOther One Nil Ok +Child Four Client DatabaseSQL Eight Two InternalError +Child Empty Unspecified DatabaseSQL One Eight NotFound +Child Empty Client DatabaseSQL One Nil OutOfRange +Child Four Server FaaSTimer Eight Empty Unauthenticated +Child Four Client DatabaseSQL One Nil AlreadyExists +Child Empty Unspecified HTTPServer Empty One InternalError +Root One Server MaxCount One One Nil +Child Four Unspecified MessagingProducer Two Nil ResourceExhausted +Child Four Client HTTPClient One Two Aborted +Child Empty Client DatabaseNoSQL Two Nil AlreadyExists +Child One Unspecified MaxCount Nil Empty DataLoss +Child One Internal Internal Empty Nil DataLoss +Child One Producer MessagingProducer One Two NotFound +Child One Unspecified FaaSTimer Two Two PermissionDenied +Root One Server FaaSOther Eight Empty Cancelled +Child Empty Client DatabaseSQL Empty One DeadlineExceeded +Child One Unspecified HTTPServer Two Eight Unimplemented +Child Four Client HTTPClient Nil Eight Nil +Root Empty Server MaxCount Nil Nil Unavailable +Child Four Internal Internal One One Aborted +Child One Unspecified FaaSHTTP Empty Nil PermissionDenied +Child One Unspecified FaaSHTTP Nil Two Unimplemented +Child One Unspecified MessagingConsumer Two Two PermissionDenied +Root One Server FaaSOther Nil Nil InvalidArgument +Child Empty Unspecified HTTPClient Empty Eight Unavailable +Child One Unspecified FaaSPubSub Eight Empty Unauthenticated +Child Empty Client gRPCClient Empty Empty AlreadyExists +Child One Unspecified DatabaseNoSQL One Empty InternalError +Root One Server FaaSHTTP One Empty Unauthenticated +Child Empty Server MaxCount Empty Empty ResourceExhausted +Child Four Client DatabaseSQL One Nil Unavailable +Root Four Server gRPCServer Nil Eight ResourceExhausted +Child Empty Internal Internal Nil Empty Unauthenticated +Child Four Unspecified HTTPServer Two Empty NotFound +Child Four Server MaxCount Two Eight Unauthenticated +Child Empty Unspecified MessagingConsumer Empty Two DeadlineExceeded +Child Four Client HTTPClient Two Two AlreadyExists +Child One Unspecified gRPCClient Nil Two NotFound +Child Empty Unspecified FaaSPubSub Nil Nil InvalidArgument +Child One Internal Internal Two Two AlreadyExists +Child Empty Consumer FaaSDatasource One Two InvalidArgument +Child Empty Server FaaSOther Nil Eight DataLoss +Child One Unspecified gRPCClient Nil Empty UnknownError +Child One Server Nil One Empty Aborted +Child Four Unspecified FaaSTimer One Two DataLoss +Child Empty Unspecified FaaSPubSub Empty One Aborted +Child One Unspecified FaaSHTTP Eight One Nil +Child One Client DatabaseSQL Eight Nil DataLoss +Child Empty Server HTTPServer Nil Eight OutOfRange +Child One Client gRPCClient Eight Two Aborted +Child One Unspecified DatabaseNoSQL Two Eight Nil +Child Four Client DatabaseNoSQL Eight Empty Aborted +Child Empty Internal Internal Eight One Unavailable +Child One Unspecified gRPCServer One Eight NotFound +Child Empty Unspecified FaaSHTTP One Two Ok +Child Four Unspecified gRPCServer One Empty UnknownError +Child Four Client DatabaseNoSQL One Nil Cancelled +Child Four Unspecified MessagingProducer Two Empty Unavailable +Child Empty Unspecified Empty Nil Eight Aborted +Child Four Server MaxCount Nil Nil DeadlineExceeded +Child Empty Client DatabaseSQL One Nil Unimplemented +Child Four Unspecified FaaSTimer Two Empty InternalError +Child Empty Unspecified DatabaseNoSQL One Eight OutOfRange +Root One Server FaaSHTTP Empty Empty Unavailable +Child One Unspecified FaaSDatasource Two Empty Cancelled +Child Empty Consumer MessagingConsumer Two One UnknownError +Child Empty Unspecified FaaSHTTP Two One FailedPrecondition +Child One Client Empty Two Nil InternalError +Root One Producer FaaSPubSub Eight Two DeadlineExceeded +Root One Producer MessagingProducer Empty Two UnknownError diff --git a/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_traces.txt b/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_traces.txt new file mode 100644 index 00000000000..6d3647b966d --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/testdata/generated_pict_pairs_traces.txt @@ -0,0 +1,33 @@ +Resource InstrumentationLibrary Spans +VMOnPrem None None +Nil One None +Exec One Several +Exec None All +Nil Two One +Empty Two Several +VMCloud Two All +K8sOnPrem None One +Empty Two None +Nil None Several +K8sOnPrem One None +K8sCloud One All +VMCloud One One +Nil None All +K8sOnPrem Two Several +K8sCloud Two One +Exec Two None +VMOnPrem Two One +K8sCloud None None +Faas One None +Faas Two Several +Exec One One +VMCloud None Several +Faas None All +Empty One One +K8sCloud None Several +VMOnPrem One All +VMOnPrem One Several +K8sOnPrem Two All +VMCloud Two None +Empty None All +Faas One One diff --git a/internal/otel_collector/internal/goldendataset/testdata/pict_input_metrics.txt b/internal/otel_collector/internal/goldendataset/testdata/pict_input_metrics.txt new file mode 100644 index 00000000000..9351db55e01 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/testdata/pict_input_metrics.txt @@ -0,0 +1,4 @@ +NumPtsPerMetric: OnePt, ManyPts +MetricType: DoubleGauge, MonotonicDoubleSum, NonMonotonicDoubleSum, IntGauge, MonotonicIntSum, NonMonotonicIntSum, IntHistogram, DoubleHistogram +NumLabels: NoLabels, OneLabel, ManyLabels +NumResourceAttrs: NoAttrs, OneAttr, TwoAttrs diff --git a/internal/otel_collector/internal/goldendataset/testdata/pict_input_spans.txt b/internal/otel_collector/internal/goldendataset/testdata/pict_input_spans.txt new file mode 100644 index 00000000000..9430ce47e3e --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/testdata/pict_input_spans.txt @@ -0,0 +1,14 @@ +Parent: Root, Child +Tracestate: Empty, One, Four +Kind: Unspecified, Internal, Server, Client, Producer, Consumer +Attributes: Nil, Empty, DatabaseSQL, DatabaseNoSQL, FaaSDatasource, FaaSHTTP, FaaSPubSub, FaaSTimer, FaaSOther, HTTPClient, HTTPServer, MessagingProducer, MessagingConsumer, gRPCClient, gRPCServer, Internal, MaxCount +Events: Nil, Empty, One, Two, Eight +Links: Nil, Empty, One, Two, Eight +Status: Nil, Ok, Cancelled, UnknownError, InvalidArgument, DeadlineExceeded, NotFound, AlreadyExists, PermissionDenied, ResourceExhausted, FailedPrecondition, Aborted, OutOfRange, Unimplemented, InternalError, Unavailable, DataLoss, Unauthenticated + +IF [Parent] = "Root" THEN [Kind] in {"Server", "Producer"}; +IF [Kind] = "Internal" THEN [Attributes] in {"Nil", "Internal"}; +IF [Kind] = "Server" THEN [Attributes] in {"Nil", "FaaSHTTP", "FaaSTimer", "FaaSOther", "HTTPServer", "gRPCServer", "MaxCount"}; +IF [Kind] = "Client" THEN [Attributes] in {"Empty", "DatabaseSQL", "DatabaseNoSQL", "HTTPClient", "gRPCClient"}; +IF [Kind] = "Producer" THEN [Attributes] in {"Empty", "MessagingProducer", "FaaSPubSub"}; +IF [Kind] = "Consumer" THEN [Attributes] in {"Nil", "MessagingConsumer", "FaaSDatasource"}; diff --git a/internal/otel_collector/internal/goldendataset/testdata/pict_input_traces.txt b/internal/otel_collector/internal/goldendataset/testdata/pict_input_traces.txt new file mode 100644 index 00000000000..ea9f40ed8a8 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/testdata/pict_input_traces.txt @@ -0,0 +1,3 @@ +Resource: Nil, Empty, VMOnPrem, VMCloud, K8sOnPrem, K8sCloud, Faas, Exec +InstrumentationLibrary: None, One, Two +Spans: None, One, Several, All diff --git a/internal/otel_collector/internal/goldendataset/traces_generator.go b/internal/otel_collector/internal/goldendataset/traces_generator.go new file mode 100644 index 00000000000..623a127dffb --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/traces_generator.go @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "fmt" + "io" + + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +// GenerateResourceSpans generates a slice of OTLP ResourceSpans objects based on the PICT-generated pairwise +// parameters defined in the parameters file specified by the tracePairsFile parameter. The pairs to generate +// spans for for defined in the file specified by the spanPairsFile parameter. The random parameter injects the +// random number generator to use in generating IDs and other random values. +// The slice of ResourceSpans are returned. If an err is returned, the slice elements will be nil. +func GenerateResourceSpans(tracePairsFile string, spanPairsFile string, + random io.Reader) ([]*otlptrace.ResourceSpans, error) { + pairsData, err := loadPictOutputFile(tracePairsFile) + if err != nil { + return nil, err + } + pairsTotal := len(pairsData) - 1 + spans := make([]*otlptrace.ResourceSpans, pairsTotal) + for index, values := range pairsData { + if index == 0 { + continue + } + tracingInputs := &PICTTracingInputs{ + Resource: PICTInputResource(values[TracesColumnResource]), + InstrumentationLibrary: PICTInputInstrumentationLibrary(values[TracesColumnInstrumentationLibrary]), + Spans: PICTInputSpans(values[TracesColumnSpans]), + } + rscSpan, spanErr := GenerateResourceSpan(tracingInputs, spanPairsFile, random) + if spanErr != nil { + err = spanErr + } + spans[index-1] = rscSpan + } + return spans, err +} + +// GenerateResourceSpan generates a single OTLP ResourceSpans populated based on the provided inputs. They are: +// tracingInputs - the pairwise combination of field value variations for this ResourceSpans +// spanPairsFile - the file with the PICT-generated parameter combinations to generate spans for +// random - the random number generator to use in generating ID values +// +// The generated resource spans. If err is not nil, some or all of the resource spans fields will be nil. +func GenerateResourceSpan(tracingInputs *PICTTracingInputs, spanPairsFile string, + random io.Reader) (*otlptrace.ResourceSpans, error) { + libSpans, err := generateLibrarySpansArray(tracingInputs, spanPairsFile, random) + return &otlptrace.ResourceSpans{ + Resource: GenerateResource(tracingInputs.Resource), + InstrumentationLibrarySpans: libSpans, + }, err +} + +func generateLibrarySpansArray(tracingInputs *PICTTracingInputs, spanPairsFile string, + random io.Reader) ([]*otlptrace.InstrumentationLibrarySpans, error) { + var count int + switch tracingInputs.InstrumentationLibrary { + case LibraryNone: + count = 1 + case LibraryOne: + count = 1 + case LibraryTwo: + count = 2 + } + var err error + libSpans := make([]*otlptrace.InstrumentationLibrarySpans, count) + for i := 0; i < count; i++ { + libSpans[i], err = generateLibrarySpans(tracingInputs, i, spanPairsFile, random) + } + return libSpans, err +} + +func generateLibrarySpans(tracingInputs *PICTTracingInputs, index int, spanPairsFile string, + random io.Reader) (*otlptrace.InstrumentationLibrarySpans, error) { + spanCaseCount, err := countTotalSpanCases(spanPairsFile) + if err != nil { + return nil, err + } + var spans []*otlptrace.Span + switch tracingInputs.Spans { + case LibrarySpansNone: + spans = make([]*otlptrace.Span, 0) + case LibrarySpansOne: + spans, _, err = GenerateSpans(1, 0, spanPairsFile, random) + case LibrarySpansSeveral: + spans, _, err = GenerateSpans(spanCaseCount/4, 0, spanPairsFile, random) + case LibrarySpansAll: + spans, _, err = GenerateSpans(spanCaseCount, 0, spanPairsFile, random) + default: + spans, _, err = GenerateSpans(16, 0, spanPairsFile, random) + } + return &otlptrace.InstrumentationLibrarySpans{ + InstrumentationLibrary: generateInstrumentationLibrary(tracingInputs, index), + Spans: spans, + }, err +} + +func countTotalSpanCases(spanPairsFile string) (int, error) { + pairsData, err := loadPictOutputFile(spanPairsFile) + if err != nil { + return 0, err + } + count := len(pairsData) - 1 + return count, err +} + +func generateInstrumentationLibrary(tracingInputs *PICTTracingInputs, index int) otlpcommon.InstrumentationLibrary { + if LibraryNone == tracingInputs.InstrumentationLibrary { + return otlpcommon.InstrumentationLibrary{} + } + nameStr := fmt.Sprintf("%s-%s-%s-%d", tracingInputs.Resource, tracingInputs.InstrumentationLibrary, + tracingInputs.Spans, index) + verStr := "semver:1.1.7" + if index > 0 { + verStr = "" + } + return otlpcommon.InstrumentationLibrary{ + Name: nameStr, + Version: verStr, + } +} diff --git a/internal/otel_collector/internal/goldendataset/traces_generator_test.go b/internal/otel_collector/internal/goldendataset/traces_generator_test.go new file mode 100644 index 00000000000..dbb8a009999 --- /dev/null +++ b/internal/otel_collector/internal/goldendataset/traces_generator_test.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goldendataset + +import ( + "io" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateTraces(t *testing.T) { + random := io.Reader(rand.New(rand.NewSource(42))) + rscSpans, err := GenerateResourceSpans("testdata/generated_pict_pairs_traces.txt", + "testdata/generated_pict_pairs_spans.txt", random) + assert.Nil(t, err) + assert.Equal(t, 32, len(rscSpans)) +} diff --git a/internal/otel_collector/internal/middleware/compression.go b/internal/otel_collector/internal/middleware/compression.go new file mode 100644 index 00000000000..13504e80953 --- /dev/null +++ b/internal/otel_collector/internal/middleware/compression.go @@ -0,0 +1,95 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package middleware + +import ( + "compress/gzip" + "compress/zlib" + "io" + "net/http" +) + +type ErrorHandler func(w http.ResponseWriter, r *http.Request, errorMsg string, statusCode int) + +type decompressor struct { + errorHandler ErrorHandler +} + +type DecompressorOption func(d *decompressor) + +func WithErrorHandler(e ErrorHandler) DecompressorOption { + return func(d *decompressor) { + d.errorHandler = e + } +} + +// HTTPContentDecompressor is a middleware that offloads the task of handling compressed +// HTTP requests by identifying the compression format in the "Content-Encoding" header and re-writing +// request body so that the handlers further in the chain can work on decompressed data. +// It supports gzip and deflate/zlib compression. +func HTTPContentDecompressor(h http.Handler, opts ...DecompressorOption) http.Handler { + d := &decompressor{} + for _, o := range opts { + o(d) + } + if d.errorHandler == nil { + d.errorHandler = defaultErrorHandler + } + return d.wrap(h) +} + +func (d *decompressor) wrap(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + newBody, err := newBodyReader(r) + if err != nil { + d.errorHandler(w, r, err.Error(), http.StatusBadRequest) + return + } + if newBody != nil { + defer newBody.Close() + // "Content-Encoding" header is removed to avoid decompressing twice + // in case the next handler(s) have implemented a similar mechanism. + r.Header.Del("Content-Encoding") + // "Content-Length" is set to -1 as the size of the decompressed body is unknown. + r.Header.Del("Content-Length") + r.ContentLength = -1 + r.Body = newBody + } + h.ServeHTTP(w, r) + }) +} + +func newBodyReader(r *http.Request) (io.ReadCloser, error) { + switch r.Header.Get("Content-Encoding") { + case "gzip": + gr, err := gzip.NewReader(r.Body) + if err != nil { + return nil, err + } + return gr, nil + case "deflate", "zlib": + zr, err := zlib.NewReader(r.Body) + if err != nil { + return nil, err + } + return zr, nil + } + return nil, nil +} + +// defaultErrorHandler writes the error message in plain text. +func defaultErrorHandler(w http.ResponseWriter, _ *http.Request, errMsg string, statusCode int) { + http.Error(w, errMsg, statusCode) +} diff --git a/internal/otel_collector/internal/middleware/compression_test.go b/internal/otel_collector/internal/middleware/compression_test.go new file mode 100644 index 00000000000..e8770ff4c3e --- /dev/null +++ b/internal/otel_collector/internal/middleware/compression_test.go @@ -0,0 +1,157 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package middleware + +import ( + "bytes" + "compress/gzip" + "compress/zlib" + "fmt" + "io/ioutil" + "net" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/testutil" +) + +func TestHTTPContentDecompressionHandler(t *testing.T) { + testBody := []byte("uncompressed_text") + tests := []struct { + name string + encoding string + reqBodyFunc func() (*bytes.Buffer, error) + respCode int + respBody string + }{ + { + name: "NoCompression", + encoding: "", + reqBodyFunc: func() (*bytes.Buffer, error) { + return bytes.NewBuffer(testBody), nil + }, + respCode: 200, + }, + { + name: "ValidGzip", + encoding: "gzip", + reqBodyFunc: func() (*bytes.Buffer, error) { + return compressGzip(testBody) + }, + respCode: 200, + }, + { + name: "ValidZlib", + encoding: "zlib", + reqBodyFunc: func() (*bytes.Buffer, error) { + return compressZlib(testBody) + }, + respCode: 200, + }, + { + name: "InvalidGzip", + encoding: "gzip", + reqBodyFunc: func() (*bytes.Buffer, error) { + return bytes.NewBuffer(testBody), nil + }, + respCode: 400, + respBody: "gzip: invalid header\n", + }, + + { + name: "InvalidZlib", + encoding: "zlib", + reqBodyFunc: func() (*bytes.Buffer, error) { + return bytes.NewBuffer(testBody), nil + }, + respCode: 400, + respBody: "zlib: invalid header\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err, "failed to read request body: %v", err) + assert.EqualValues(t, testBody, string(body)) + w.WriteHeader(200) + }) + + addr := testutil.GetAvailableLocalAddress(t) + ln, err := net.Listen("tcp", addr) + require.NoError(t, err, "failed to create listener: %v", err) + srv := &http.Server{ + Handler: HTTPContentDecompressor(handler), + } + go func() { + _ = srv.Serve(ln) + }() + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + serverURL := fmt.Sprintf("http://%s", ln.Addr().String()) + reqBody, err := tt.reqBodyFunc() + require.NoError(t, err, "failed to generate request body: %v", err) + + req, err := http.NewRequest("GET", serverURL, reqBody) + require.NoError(t, err, "failed to create request to test handler") + req.Header.Set("Content-Encoding", tt.encoding) + + client := http.Client{} + res, err := client.Do(req) + require.NoError(t, err) + + assert.Equal(t, tt.respCode, res.StatusCode, "test handler returned unexpected status code ") + if tt.respBody != "" { + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, res.Body.Close(), "failed to close request body: %v", err) + assert.Equal(t, tt.respBody, string(body)) + } + require.NoError(t, srv.Close()) + }) + } +} + +func compressGzip(body []byte) (*bytes.Buffer, error) { + var buf bytes.Buffer + + gw := gzip.NewWriter(&buf) + defer gw.Close() + + _, err := gw.Write(body) + if err != nil { + return nil, err + } + + return &buf, nil +} + +func compressZlib(body []byte) (*bytes.Buffer, error) { + var buf bytes.Buffer + + zw := zlib.NewWriter(&buf) + defer zw.Close() + + _, err := zw.Write(body) + if err != nil { + return nil, err + } + + return &buf, nil +} diff --git a/internal/otel_collector/internal/otlp_wrapper.go b/internal/otel_collector/internal/otlp_wrapper.go new file mode 100644 index 00000000000..5b1fa36bd09 --- /dev/null +++ b/internal/otel_collector/internal/otlp_wrapper.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" + +// OtlpLogsWrapper is an intermediary struct that is declared in an internal package +// as a way to prevent certain functions of pdata.Logs data type to be callable by +// any code outside of this module. +type OtlpLogsWrapper struct { + Orig *[]*otlplogs.ResourceLogs +} + +func LogsToOtlp(l OtlpLogsWrapper) []*otlplogs.ResourceLogs { + return *l.Orig +} + +func LogsFromOtlp(logs []*otlplogs.ResourceLogs) OtlpLogsWrapper { + return OtlpLogsWrapper{Orig: &logs} +} diff --git a/internal/otel_collector/internal/processor/filterconfig/config.go b/internal/otel_collector/internal/processor/filterconfig/config.go new file mode 100644 index 00000000000..c18f23beedf --- /dev/null +++ b/internal/otel_collector/internal/processor/filterconfig/config.go @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterconfig + +import ( + "errors" + + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +// MatchConfig has two optional MatchProperties one to define what is processed +// by the processor, captured under the 'include' and the second, exclude, to +// define what is excluded from the processor. +type MatchConfig struct { + // Include specifies the set of span/log properties that must be present in order + // for this processor to apply to it. + // Note: If `exclude` is specified, the span/log is compared against those + // properties after the `include` properties. + // This is an optional field. If neither `include` and `exclude` are set, all span/logs + // are processed. If `include` is set and `exclude` isn't set, then all + // span/logs matching the properties in this structure are processed. + Include *MatchProperties `mapstructure:"include"` + + // Exclude specifies when this processor will not be applied to the span/logs + // which match the specified properties. + // Note: The `exclude` properties are checked after the `include` properties, + // if they exist, are checked. + // If `include` isn't specified, the `exclude` properties are checked against + // all span/logs. + // This is an optional field. If neither `include` and `exclude` are set, all span/logs + // are processed. If `exclude` is set and `include` isn't set, then all + // span/logs that do no match the properties in this structure are processed. + Exclude *MatchProperties `mapstructure:"exclude"` +} + +// MatchProperties specifies the set of properties in a span/log to match +// against and if the span/log should be included or excluded from the +// processor. At least one of services (spans only), span/log names or +// attributes must be specified. It is supported to have all specified, but +// this requires all of the properties to match for the inclusion/exclusion to +// occur. +// The following are examples of invalid configurations: +// attributes/bad1: +// # This is invalid because include is specified with neither services or +// # attributes. +// include: +// actions: ... +// +// span/bad2: +// exclude: +// # This is invalid because services, span_names and attributes have empty values. +// services: +// span_names: +// attributes: +// actions: ... +// Please refer to processor/attributesprocessor/testdata/config.yaml and +// processor/spanprocessor/testdata/config.yaml for valid configurations. +type MatchProperties struct { + // Config configures the matching patterns used when matching span properties. + filterset.Config `mapstructure:",squash"` + + // Note: For spans, one of Services, SpanNames, Attributes, Resources or Libraries must be specified with a + // non-empty value for a valid configuration. + + // For logs, one of LogNames, Attributes, Resources or Libraries must be specified with a + // non-empty value for a valid configuration. + + // Services specify the list of of items to match service name against. + // A match occurs if the span's service name matches at least one item in this list. + // This is an optional field. + Services []string `mapstructure:"services"` + + // SpanNames specify the list of items to match span name against. + // A match occurs if the span name matches at least one item in this list. + // This is an optional field. + SpanNames []string `mapstructure:"span_names"` + + // LogNames is a list of strings that the LogRecord's name field must match + // against. + LogNames []string `mapstructure:"log_names"` + + // Attributes specifies the list of attributes to match against. + // All of these attributes must match exactly for a match to occur. + // Only match_type=strict is allowed if "attributes" are specified. + // This is an optional field. + Attributes []Attribute `mapstructure:"attributes"` + + // Resources specify the list of items to match the resources against. + // A match occurs if the span's resources matches at least one item in this list. + // This is an optional field. + Resources []Attribute `mapstructure:"resources"` + + // Libraries specify the list of items to match the implementation library against. + // A match occurs if the span's implementation library matches at least one item in this list. + // This is an optional field. + Libraries []InstrumentationLibrary `mapstructure:"libraries"` +} + +func (mp *MatchProperties) ValidateForSpans() error { + if len(mp.LogNames) > 0 { + return errors.New("log_names should not be specified for trace spans") + } + + if len(mp.Services) == 0 && len(mp.SpanNames) == 0 && len(mp.Attributes) == 0 && + len(mp.Libraries) == 0 && len(mp.Resources) == 0 { + return errors.New(`at least one of "services", "span_names", "attributes", "libraries" or "resources" field must be specified`) + } + + return nil +} + +func (mp *MatchProperties) ValidateForLogs() error { + if len(mp.SpanNames) > 0 || len(mp.Services) > 0 { + return errors.New("neither services nor span_names should be specified for log records") + } + + if len(mp.LogNames) == 0 && len(mp.Attributes) == 0 && len(mp.Libraries) == 0 && len(mp.Resources) == 0 { + return errors.New(`at least one of "log_names", "attributes", "libraries" or "resources" field must be specified`) + } + + return nil +} + +// MatchTypeFieldName is the mapstructure field name for MatchProperties.Attributes field. +const AttributesFieldName = "attributes" + +// Attribute specifies the attribute key and optional value to match against. +type Attribute struct { + // Key specifies the attribute key. + Key string `mapstructure:"key"` + + // Values specifies the value to match against. + // If it is not set, any value will match. + Value interface{} `mapstructure:"value"` +} + +// InstrumentationLibrary specifies the instrumentation library and optional version to match against. +type InstrumentationLibrary struct { + Name string `mapstructure:"name"` + // version match + // expected actual match + // nil yes + // nil 1 yes + // yes + // 1 no + // 1 no + // 1 1 yes + Version *string `mapstructure:"version"` +} diff --git a/internal/otel_collector/internal/processor/filterconfig/config_test.go b/internal/otel_collector/internal/processor/filterconfig/config_test.go new file mode 100644 index 00000000000..985f71ae908 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterconfig/config_test.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterconfig diff --git a/internal/otel_collector/internal/processor/filterexpr/matcher.go b/internal/otel_collector/internal/processor/filterexpr/matcher.go new file mode 100644 index 00000000000..8f92c59af3c --- /dev/null +++ b/internal/otel_collector/internal/processor/filterexpr/matcher.go @@ -0,0 +1,172 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterexpr + +import ( + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/vm" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +type Matcher struct { + program *vm.Program + v vm.VM +} + +type env struct { + MetricName string + // TODO: replace this with GetLabel func(key string) (string,bool) + HasLabel func(key string) bool + Label func(key string) string +} + +func NewMatcher(expression string) (*Matcher, error) { + program, err := expr.Compile(expression) + if err != nil { + return nil, err + } + return &Matcher{program: program, v: vm.VM{}}, nil +} + +func (m *Matcher) MatchMetric(metric pdata.Metric) (bool, error) { + metricName := metric.Name() + switch metric.DataType() { + case pdata.MetricDataTypeIntGauge: + return m.matchIntGauge(metricName, metric.IntGauge()) + case pdata.MetricDataTypeDoubleGauge: + return m.matchDoubleGauge(metricName, metric.DoubleGauge()) + case pdata.MetricDataTypeIntSum: + return m.matchIntSum(metricName, metric.IntSum()) + case pdata.MetricDataTypeDoubleSum: + return m.matchDoubleSum(metricName, metric.DoubleSum()) + case pdata.MetricDataTypeIntHistogram: + return m.matchIntHistogram(metricName, metric.IntHistogram()) + case pdata.MetricDataTypeDoubleHistogram: + return m.matchDoubleHistogram(metricName, metric.DoubleHistogram()) + default: + return false, nil + } +} + +func (m *Matcher) matchIntGauge(metricName string, gauge pdata.IntGauge) (bool, error) { + pts := gauge.DataPoints() + for i := 0; i < pts.Len(); i++ { + matched, err := m.matchEnv(metricName, pts.At(i).LabelsMap()) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} + +func (m *Matcher) matchDoubleGauge(metricName string, gauge pdata.DoubleGauge) (bool, error) { + pts := gauge.DataPoints() + for i := 0; i < pts.Len(); i++ { + matched, err := m.matchEnv(metricName, pts.At(i).LabelsMap()) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} + +func (m *Matcher) matchDoubleSum(metricName string, sum pdata.DoubleSum) (bool, error) { + pts := sum.DataPoints() + for i := 0; i < pts.Len(); i++ { + matched, err := m.matchEnv(metricName, pts.At(i).LabelsMap()) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} + +func (m *Matcher) matchIntSum(metricName string, sum pdata.IntSum) (bool, error) { + pts := sum.DataPoints() + for i := 0; i < pts.Len(); i++ { + matched, err := m.matchEnv(metricName, pts.At(i).LabelsMap()) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} + +func (m *Matcher) matchIntHistogram(metricName string, histogram pdata.IntHistogram) (bool, error) { + pts := histogram.DataPoints() + for i := 0; i < pts.Len(); i++ { + matched, err := m.matchEnv(metricName, pts.At(i).LabelsMap()) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} + +func (m *Matcher) matchDoubleHistogram(metricName string, histogram pdata.DoubleHistogram) (bool, error) { + pts := histogram.DataPoints() + for i := 0; i < pts.Len(); i++ { + matched, err := m.matchEnv(metricName, pts.At(i).LabelsMap()) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} + +func (m *Matcher) matchEnv(metricName string, labelsMap pdata.StringMap) (bool, error) { + return m.match(createEnv(metricName, labelsMap)) +} + +func createEnv(metricName string, labelsMap pdata.StringMap) env { + return env{ + MetricName: metricName, + HasLabel: func(key string) bool { + _, ok := labelsMap.Get(key) + return ok + }, + Label: func(key string) string { + v, _ := labelsMap.Get(key) + return v + }, + } +} + +func (m *Matcher) match(env env) (bool, error) { + result, err := m.v.Run(m.program, env) + if err != nil { + return false, err + } + return result.(bool), nil +} diff --git a/internal/otel_collector/internal/processor/filterexpr/matcher_test.go b/internal/otel_collector/internal/processor/filterexpr/matcher_test.go new file mode 100644 index 00000000000..3eb5829bb45 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterexpr/matcher_test.go @@ -0,0 +1,345 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterexpr + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestCompileExprError(t *testing.T) { + _, err := NewMatcher("") + require.Error(t, err) +} + +func TestRunExprError(t *testing.T) { + matcher, err := NewMatcher("foo") + require.NoError(t, err) + matched, _ := matcher.match(env{}) + require.False(t, matched) +} + +func TestUnknownDataType(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(-1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.False(t, matched) +} + +func TestNilIntGauge(t *testing.T) { + dataType := pdata.MetricDataTypeIntGauge + testNilValue(t, dataType) +} + +func TestNilDoubleGauge(t *testing.T) { + dataType := pdata.MetricDataTypeDoubleGauge + testNilValue(t, dataType) +} + +func TestNilDoubleSum(t *testing.T) { + dataType := pdata.MetricDataTypeDoubleSum + testNilValue(t, dataType) +} + +func TestNilIntSum(t *testing.T) { + dataType := pdata.MetricDataTypeIntSum + testNilValue(t, dataType) +} + +func TestNilIntHistogram(t *testing.T) { + dataType := pdata.MetricDataTypeIntHistogram + testNilValue(t, dataType) +} + +func TestNilDoubleHistogram(t *testing.T) { + dataType := pdata.MetricDataTypeDoubleHistogram + testNilValue(t, dataType) +} + +func testNilValue(t *testing.T, dataType pdata.MetricDataType) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(dataType) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.False(t, matched) +} + +func TestIntGaugeEmptyDataPoint(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeIntGauge) + dps := m.IntGauge().DataPoints() + dps.Resize(1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestDoubleGaugeEmptyDataPoint(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeDoubleGauge) + dps := m.DoubleGauge().DataPoints() + dps.Resize(1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestDoubleSumEmptyDataPoint(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeDoubleSum) + dps := m.DoubleSum().DataPoints() + dps.Resize(1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestIntSumEmptyDataPoint(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeIntSum) + dps := m.IntSum().DataPoints() + dps.Resize(1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestIntHistogramEmptyDataPoint(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeIntHistogram) + dps := m.IntHistogram().DataPoints() + dps.Resize(1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestDoubleHistogramEmptyDataPoint(t *testing.T) { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeDoubleHistogram) + dps := m.DoubleHistogram().DataPoints() + dps.Resize(1) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestMatchIntGaugeByMetricName(t *testing.T) { + expression := `MetricName == 'my.metric'` + assert.True(t, testMatchIntGauge(t, "my.metric", expression, nil)) +} + +func TestNonMatchIntGaugeByMetricName(t *testing.T) { + expression := `MetricName == 'my.metric'` + assert.False(t, testMatchIntGauge(t, "foo.metric", expression, nil)) +} + +func TestNonMatchIntGaugeDataPointByMetricAndHasLabel(t *testing.T) { + expression := `MetricName == 'my.metric' && HasLabel("foo")` + assert.False(t, testMatchIntGauge(t, "foo.metric", expression, nil)) +} + +func TestMatchIntGaugeDataPointByMetricAndHasLabel(t *testing.T) { + expression := `MetricName == 'my.metric' && HasLabel("foo")` + assert.True(t, testMatchIntGauge(t, "my.metric", expression, map[string]string{"foo": ""})) +} + +func TestMatchIntGaugeDataPointByMetricAndLabelValue(t *testing.T) { + expression := `MetricName == 'my.metric' && Label("foo") == "bar"` + assert.False(t, testMatchIntGauge(t, "my.metric", expression, map[string]string{"foo": ""})) +} + +func TestNonMatchIntGaugeDataPointByMetricAndLabelValue(t *testing.T) { + expression := `MetricName == 'my.metric' && Label("foo") == "bar"` + assert.False(t, testMatchIntGauge(t, "my.metric", expression, map[string]string{"foo": ""})) +} + +func testMatchIntGauge(t *testing.T, metricName, expression string, lbls map[string]string) bool { + matcher, err := NewMatcher(expression) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName(metricName) + m.SetDataType(pdata.MetricDataTypeIntGauge) + dps := m.IntGauge().DataPoints() + dps.Resize(1) + pt := dps.At(0) + if lbls != nil { + pt.LabelsMap().InitFromMap(lbls) + } + match, err := matcher.MatchMetric(m) + assert.NoError(t, err) + return match +} + +func TestMatchIntGaugeDataPointByMetricAndSecondPointLabelValue(t *testing.T) { + matcher, err := NewMatcher( + `MetricName == 'my.metric' && Label("baz") == "glarch"`, + ) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName("my.metric") + m.SetDataType(pdata.MetricDataTypeIntGauge) + dps := m.IntGauge().DataPoints() + dps.Resize(2) + + pt1 := dps.At(0) + pt1.LabelsMap().Insert("foo", "bar") + + pt2 := dps.At(1) + pt2.LabelsMap().Insert("baz", "glarch") + + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + assert.True(t, matched) +} + +func TestMatchDoubleGaugeByMetricName(t *testing.T) { + assert.True(t, testMatchDoubleGauge(t, "my.metric")) +} + +func TestNonMatchDoubleGaugeByMetricName(t *testing.T) { + assert.False(t, testMatchDoubleGauge(t, "foo.metric")) +} + +func testMatchDoubleGauge(t *testing.T, metricName string) bool { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName(metricName) + m.SetDataType(pdata.MetricDataTypeDoubleGauge) + dps := m.DoubleGauge().DataPoints() + pt := pdata.NewDoubleDataPoint() + dps.Append(pt) + match, err := matcher.MatchMetric(m) + assert.NoError(t, err) + return match +} + +func TestMatchDoubleSumByMetricName(t *testing.T) { + assert.True(t, matchDoubleSum(t, "my.metric")) +} + +func TestNonMatchDoubleSumByMetricName(t *testing.T) { + assert.False(t, matchDoubleSum(t, "foo.metric")) +} + +func matchDoubleSum(t *testing.T, metricName string) bool { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName(metricName) + m.SetDataType(pdata.MetricDataTypeDoubleSum) + dps := m.DoubleSum().DataPoints() + pt := pdata.NewDoubleDataPoint() + dps.Append(pt) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + return matched +} + +func TestMatchIntSumByMetricName(t *testing.T) { + assert.True(t, matchIntSum(t, "my.metric")) +} + +func TestNonMatchIntSumByMetricName(t *testing.T) { + assert.False(t, matchIntSum(t, "foo.metric")) +} + +func matchIntSum(t *testing.T, metricName string) bool { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName(metricName) + m.SetDataType(pdata.MetricDataTypeIntSum) + dps := m.IntSum().DataPoints() + pt := pdata.NewIntDataPoint() + dps.Append(pt) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + return matched +} + +func TestMatchIntHistogramByMetricName(t *testing.T) { + assert.True(t, matchIntHistogram(t, "my.metric")) +} + +func TestNonMatchIntHistogramByMetricName(t *testing.T) { + assert.False(t, matchIntHistogram(t, "foo.metric")) +} + +func matchIntHistogram(t *testing.T, metricName string) bool { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName(metricName) + m.SetDataType(pdata.MetricDataTypeIntHistogram) + dps := m.IntHistogram().DataPoints() + pt := pdata.NewIntHistogramDataPoint() + dps.Append(pt) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + return matched +} + +func TestMatchDoubleHistogramByMetricName(t *testing.T) { + assert.True(t, matchDoubleHistogram(t, "my.metric")) +} + +func TestNonMatchDoubleHistogramByMetricName(t *testing.T) { + assert.False(t, matchDoubleHistogram(t, "foo.metric")) +} + +func matchDoubleHistogram(t *testing.T, metricName string) bool { + matcher, err := NewMatcher(`MetricName == 'my.metric'`) + require.NoError(t, err) + m := pdata.NewMetric() + m.SetName(metricName) + m.SetDataType(pdata.MetricDataTypeDoubleHistogram) + dps := m.DoubleHistogram().DataPoints() + pt := pdata.NewDoubleHistogramDataPoint() + dps.Append(pt) + matched, err := matcher.MatchMetric(m) + assert.NoError(t, err) + return matched +} diff --git a/internal/otel_collector/internal/processor/filterhelper/filterhelper.go b/internal/otel_collector/internal/processor/filterhelper/filterhelper.go new file mode 100644 index 00000000000..fca006d36c4 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterhelper/filterhelper.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterhelper + +import ( + "fmt" + + "github.com/spf13/cast" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// NewAttributeValueRaw is used to convert the raw `value` from ActionKeyValue to the supported trace attribute values. +// If error different than nil the return value is invalid. Calling any functions on the invalid value will cause a panic. +func NewAttributeValueRaw(value interface{}) (pdata.AttributeValue, error) { + switch val := value.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return pdata.NewAttributeValueInt(cast.ToInt64(val)), nil + case float32, float64: + return pdata.NewAttributeValueDouble(cast.ToFloat64(val)), nil + case string: + return pdata.NewAttributeValueString(val), nil + case bool: + return pdata.NewAttributeValueBool(val), nil + default: + return pdata.AttributeValue{}, fmt.Errorf("error unsupported value type \"%T\"", value) + } +} diff --git a/internal/otel_collector/internal/processor/filterhelper/filterhelper_test.go b/internal/otel_collector/internal/processor/filterhelper/filterhelper_test.go new file mode 100644 index 00000000000..6d0eeba9886 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterhelper/filterhelper_test.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterhelper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestHelper_AttributeValue(t *testing.T) { + val, err := NewAttributeValueRaw(uint8(123)) + assert.Equal(t, pdata.NewAttributeValueInt(123), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw(uint16(123)) + assert.Equal(t, pdata.NewAttributeValueInt(123), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw(int8(123)) + assert.Equal(t, pdata.NewAttributeValueInt(123), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw(int16(123)) + assert.Equal(t, pdata.NewAttributeValueInt(123), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw(float32(234.129312)) + assert.Equal(t, pdata.NewAttributeValueDouble(float64(float32(234.129312))), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw(234.129312) + assert.Equal(t, pdata.NewAttributeValueDouble(234.129312), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw(true) + assert.Equal(t, pdata.NewAttributeValueBool(true), val) + assert.NoError(t, err) + + val, err = NewAttributeValueRaw("bob the builder") + assert.Equal(t, pdata.NewAttributeValueString("bob the builder"), val) + assert.NoError(t, err) + + _, err = NewAttributeValueRaw(nil) + assert.Error(t, err) + + _, err = NewAttributeValueRaw(t) + assert.Error(t, err) +} diff --git a/internal/otel_collector/internal/processor/filterlog/filterlog.go b/internal/otel_collector/internal/processor/filterlog/filterlog.go new file mode 100644 index 00000000000..132afaaf1c1 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterlog/filterlog.go @@ -0,0 +1,84 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterlog + +import ( + "fmt" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filtermatcher" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +// TODO: Modify Matcher to invoke both the include and exclude properties so +// calling processors will always have the same logic. +// Matcher is an interface that allows matching a log record against a +// configuration of a match. +type Matcher interface { + MatchLogRecord(lr pdata.LogRecord, resource pdata.Resource, library pdata.InstrumentationLibrary) bool +} + +// propertiesMatcher allows matching a log record against various log record properties. +type propertiesMatcher struct { + filtermatcher.PropertiesMatcher + + // log names to compare to. + nameFilters filterset.FilterSet +} + +// NewMatcher creates a LogRecord Matcher that matches based on the given MatchProperties. +func NewMatcher(mp *filterconfig.MatchProperties) (Matcher, error) { + if mp == nil { + return nil, nil + } + + if err := mp.ValidateForLogs(); err != nil { + return nil, err + } + + rm, err := filtermatcher.NewMatcher(mp) + if err != nil { + return nil, err + } + + var nameFS filterset.FilterSet = nil + if len(mp.LogNames) > 0 { + nameFS, err = filterset.CreateFilterSet(mp.LogNames, &mp.Config) + if err != nil { + return nil, fmt.Errorf("error creating log record name filters: %v", err) + } + } + + return &propertiesMatcher{ + PropertiesMatcher: rm, + nameFilters: nameFS, + }, nil +} + +// MatchLogRecord matches a log record to a set of properties. +// There are 3 sets of properties to match against. +// The log record names are matched, if specified. +// The attributes are then checked, if specified. +// At least one of log record names or attributes must be specified. It is +// supported to have more than one of these specified, and all specified must +// evaluate to true for a match to occur. +func (mp *propertiesMatcher) MatchLogRecord(lr pdata.LogRecord, resource pdata.Resource, library pdata.InstrumentationLibrary) bool { + if mp.nameFilters != nil && !mp.nameFilters.Matches(lr.Name()) { + return false + } + + return mp.PropertiesMatcher.Match(lr.Attributes(), resource, library) +} diff --git a/internal/otel_collector/internal/processor/filterlog/filterlog_test.go b/internal/otel_collector/internal/processor/filterlog/filterlog_test.go new file mode 100644 index 00000000000..ef14afed163 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterlog/filterlog_test.go @@ -0,0 +1,183 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterlog + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +func createConfig(matchType filterset.MatchType) *filterset.Config { + return &filterset.Config{ + MatchType: matchType, + } +} + +func TestLogRecord_validateMatchesConfiguration_InvalidConfig(t *testing.T) { + testcases := []struct { + name string + property filterconfig.MatchProperties + errorString string + }{ + { + name: "empty_property", + property: filterconfig.MatchProperties{}, + errorString: "at least one of \"log_names\", \"attributes\", \"libraries\" or \"resources\" field must be specified", + }, + { + name: "empty_log_names_and_attributes", + property: filterconfig.MatchProperties{ + LogNames: []string{}, + }, + errorString: "at least one of \"log_names\", \"attributes\", \"libraries\" or \"resources\" field must be specified", + }, + { + name: "span_properties", + property: filterconfig.MatchProperties{ + SpanNames: []string{"span"}, + }, + errorString: "neither services nor span_names should be specified for log records", + }, + { + name: "invalid_match_type", + property: filterconfig.MatchProperties{ + Config: *createConfig("wrong_match_type"), + LogNames: []string{"abc"}, + }, + errorString: "error creating log record name filters: unrecognized match_type: 'wrong_match_type', valid types are: [regexp strict]", + }, + { + name: "missing_match_type", + property: filterconfig.MatchProperties{ + LogNames: []string{"abc"}, + }, + errorString: "error creating log record name filters: unrecognized match_type: '', valid types are: [regexp strict]", + }, + { + name: "invalid_regexp_pattern", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogNames: []string{"["}, + }, + errorString: "error creating log record name filters: error parsing regexp: missing closing ]: `[`", + }, + { + name: "invalid_regexp_pattern2", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogNames: []string{"["}, + }, + errorString: "error creating log record name filters: error parsing regexp: missing closing ]: `[`", + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + output, err := NewMatcher(&tc.property) + assert.Nil(t, output) + require.NotNil(t, err) + assert.Equal(t, tc.errorString, err.Error()) + }) + } +} + +func TestLogRecord_Matching_False(t *testing.T) { + testcases := []struct { + name string + properties *filterconfig.MatchProperties + }{ + { + name: "log_name_doesnt_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogNames: []string{"logNo.*Name"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + + { + name: "log_name_doesnt_match_any", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogNames: []string{ + "logNo.*Name", + "non-matching?pattern", + "regular string", + }, + Attributes: []filterconfig.Attribute{}, + }, + }, + } + + lr := pdata.NewLogRecord() + lr.SetName("logName") + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + matcher, err := NewMatcher(tc.properties) + assert.Nil(t, err) + assert.NotNil(t, matcher) + + assert.False(t, matcher.MatchLogRecord(lr, pdata.Resource{}, pdata.InstrumentationLibrary{})) + }) + } +} + +func TestLogRecord_Matching_True(t *testing.T) { + testcases := []struct { + name string + properties *filterconfig.MatchProperties + }{ + { + name: "log_name_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogNames: []string{"log.*"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "log_name_second_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogNames: []string{ + "wrong.*pattern", + "log.*", + "yet another?pattern", + "regularstring", + }, + Attributes: []filterconfig.Attribute{}, + }, + }, + } + + lr := pdata.NewLogRecord() + lr.SetName("logName") + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + mp, err := NewMatcher(tc.properties) + assert.Nil(t, err) + assert.NotNil(t, mp) + + assert.NotNil(t, lr) + assert.True(t, mp.MatchLogRecord(lr, pdata.Resource{}, pdata.InstrumentationLibrary{})) + }) + } +} diff --git a/internal/otel_collector/internal/processor/filtermatcher/attributematcher.go b/internal/otel_collector/internal/processor/filtermatcher/attributematcher.go new file mode 100644 index 00000000000..7f02ec5809c --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermatcher/attributematcher.go @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermatcher + +import ( + "errors" + "fmt" + "strconv" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterhelper" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +type attributesMatcher []attributeMatcher + +// attributeMatcher is a attribute key/value pair to match to. +type attributeMatcher struct { + Key string + // If both AttributeValue and StringFilter are nil only check for key existence. + AttributeValue *pdata.AttributeValue + // StringFilter is needed to match against a regular expression + StringFilter filterset.FilterSet +} + +var errUnexpectedAttributeType = errors.New("unexpected attribute type") + +func newAttributesMatcher(config filterset.Config, attributes []filterconfig.Attribute) (attributesMatcher, error) { + // Convert attribute values from mp representation to in-memory representation. + var rawAttributes []attributeMatcher + for _, attribute := range attributes { + + if attribute.Key == "" { + return nil, errors.New("can't have empty key in the list of attributes") + } + + entry := attributeMatcher{ + Key: attribute.Key, + } + if attribute.Value != nil { + val, err := filterhelper.NewAttributeValueRaw(attribute.Value) + if err != nil { + return nil, err + } + + if config.MatchType == filterset.Regexp { + if val.Type() != pdata.AttributeValueSTRING { + return nil, fmt.Errorf( + "%s=%s for %q only supports STRING, but found %s", + filterset.MatchTypeFieldName, filterset.Regexp, attribute.Key, val.Type(), + ) + } + + filter, err := filterset.CreateFilterSet([]string{val.StringVal()}, &config) + if err != nil { + return nil, err + } + entry.StringFilter = filter + } else { + entry.AttributeValue = &val + } + } + + rawAttributes = append(rawAttributes, entry) + } + return rawAttributes, nil +} + +// match attributes specification against a span/log. +func (ma attributesMatcher) Match(attrs pdata.AttributeMap) bool { + // If there are no attributes to match against, the span/log matches. + if len(ma) == 0 { + return true + } + + // At this point, it is expected of the span/log to have attributes because of + // len(ma) != 0. This means for spans/logs with no attributes, it does not match. + if attrs.Len() == 0 { + return false + } + + // Check that all expected properties are set. + for _, property := range ma { + attr, exist := attrs.Get(property.Key) + if !exist { + return false + } + + if property.StringFilter != nil { + value, err := attributeStringValue(attr) + if err != nil || !property.StringFilter.Matches(value) { + return false + } + } else if property.AttributeValue != nil { + if !attr.Equal(*property.AttributeValue) { + return false + } + } + } + return true +} + +func attributeStringValue(attr pdata.AttributeValue) (string, error) { + switch attr.Type() { + case pdata.AttributeValueSTRING: + return attr.StringVal(), nil + case pdata.AttributeValueBOOL: + return strconv.FormatBool(attr.BoolVal()), nil + case pdata.AttributeValueDOUBLE: + return strconv.FormatFloat(attr.DoubleVal(), 'f', -1, 64), nil + case pdata.AttributeValueINT: + return strconv.FormatInt(attr.IntVal(), 10), nil + default: + return "", errUnexpectedAttributeType + } +} diff --git a/internal/otel_collector/internal/processor/filtermatcher/filtermatcher.go b/internal/otel_collector/internal/processor/filtermatcher/filtermatcher.go new file mode 100644 index 00000000000..5edba8f49fe --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermatcher/filtermatcher.go @@ -0,0 +1,103 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermatcher + +import ( + "fmt" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +type instrumentationLibraryMatcher struct { + Name filterset.FilterSet + Version filterset.FilterSet +} + +// propertiesMatcher allows matching a span against various span properties. +type PropertiesMatcher struct { + // Instrumentation libraries to compare against + libraries []instrumentationLibraryMatcher + + // The attribute values are stored in the internal format. + attributes attributesMatcher + + // The attribute values are stored in the internal format. + resources attributesMatcher +} + +// NewMatcher creates a span Matcher that matches based on the given MatchProperties. +func NewMatcher(mp *filterconfig.MatchProperties) (PropertiesMatcher, error) { + var lm []instrumentationLibraryMatcher + for _, library := range mp.Libraries { + name, err := filterset.CreateFilterSet([]string{library.Name}, &mp.Config) + if err != nil { + return PropertiesMatcher{}, fmt.Errorf("error creating library name filters: %v", err) + } + + var version filterset.FilterSet + if library.Version != nil { + filter, err := filterset.CreateFilterSet([]string{*library.Version}, &mp.Config) + if err != nil { + return PropertiesMatcher{}, fmt.Errorf("error creating library version filters: %v", err) + } + version = filter + } + + lm = append(lm, instrumentationLibraryMatcher{Name: name, Version: version}) + } + + var err error + var am attributesMatcher + if len(mp.Attributes) > 0 { + am, err = newAttributesMatcher(mp.Config, mp.Attributes) + if err != nil { + return PropertiesMatcher{}, fmt.Errorf("error creating attribute filters: %v", err) + } + } + + var rm attributesMatcher + if len(mp.Resources) > 0 { + rm, err = newAttributesMatcher(mp.Config, mp.Resources) + if err != nil { + return PropertiesMatcher{}, fmt.Errorf("error creating resource filters: %v", err) + } + } + + return PropertiesMatcher{ + libraries: lm, + attributes: am, + resources: rm, + }, nil +} + +// Match matches a span or log to a set of properties. +func (mp *PropertiesMatcher) Match(attributes pdata.AttributeMap, resource pdata.Resource, library pdata.InstrumentationLibrary) bool { + for _, matcher := range mp.libraries { + if !matcher.Name.Matches(library.Name()) { + return false + } + if matcher.Version != nil && !matcher.Version.Matches(library.Version()) { + return false + } + } + + if mp.resources != nil && !mp.resources.Match(resource.Attributes()) { + return false + } + + return mp.attributes.Match(attributes) +} diff --git a/internal/otel_collector/internal/processor/filtermatcher/filtermatcher_test.go b/internal/otel_collector/internal/processor/filtermatcher/filtermatcher_test.go new file mode 100644 index 00000000000..31102381d71 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermatcher/filtermatcher_test.go @@ -0,0 +1,394 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermatcher + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/translator/conventions" +) + +func createConfig(matchType filterset.MatchType) *filterset.Config { + return &filterset.Config{ + MatchType: matchType, + } +} + +func Test_validateMatchesConfiguration_InvalidConfig(t *testing.T) { + version := "[" + testcases := []struct { + name string + property filterconfig.MatchProperties + errorString string + }{ + { + name: "regexp_match_type_for_int_attribute", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Attributes: []filterconfig.Attribute{ + {Key: "key", Value: 1}, + }, + }, + errorString: `error creating attribute filters: match_type=regexp for "key" only supports STRING, but found INT`, + }, + { + name: "unknown_attribute_value", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Attributes: []filterconfig.Attribute{ + {Key: "key", Value: []string{}}, + }, + }, + errorString: `error creating attribute filters: error unsupported value type "[]string"`, + }, + { + name: "invalid_regexp_pattern_attribute", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Attributes: []filterconfig.Attribute{{Key: "key", Value: "["}}, + }, + errorString: "error creating attribute filters: error parsing regexp: missing closing ]: `[`", + }, + { + name: "invalid_regexp_pattern_resource", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Resources: []filterconfig.Attribute{{Key: "key", Value: "["}}, + }, + errorString: "error creating resource filters: error parsing regexp: missing closing ]: `[`", + }, + { + name: "invalid_regexp_pattern_library_name", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Libraries: []filterconfig.InstrumentationLibrary{{Name: "["}}, + }, + errorString: "error creating library name filters: error parsing regexp: missing closing ]: `[`", + }, + { + name: "invalid_regexp_pattern_library_version", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Libraries: []filterconfig.InstrumentationLibrary{{Name: "lib", Version: &version}}, + }, + errorString: "error creating library version filters: error parsing regexp: missing closing ]: `[`", + }, + { + name: "empty_key_name_in_attributes_list", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"a"}, + Attributes: []filterconfig.Attribute{ + { + Key: "", + }, + }, + }, + errorString: "error creating attribute filters: can't have empty key in the list of attributes", + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + output, err := NewMatcher(&tc.property) + assert.Zero(t, output) + assert.EqualError(t, err, tc.errorString) + }) + } +} + +func Test_Matching_False(t *testing.T) { + version := "wrong" + testcases := []struct { + name string + properties *filterconfig.MatchProperties + }{ + { + name: "wrong_library_name", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Libraries: []filterconfig.InstrumentationLibrary{{Name: "wrong"}}, + }, + }, + { + name: "wrong_library_version", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Libraries: []filterconfig.InstrumentationLibrary{{Name: "lib", Version: &version}}, + }, + }, + + { + name: "wrong_attribute_value", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Attributes: []filterconfig.Attribute{ + { + Key: "keyInt", + Value: 1234, + }, + }, + }, + }, + { + name: "wrong_resource_value", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Resources: []filterconfig.Attribute{ + { + Key: "keyInt", + Value: 1234, + }, + }, + }, + }, + { + name: "incompatible_attribute_value", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Attributes: []filterconfig.Attribute{ + { + Key: "keyInt", + Value: "123", + }, + }, + }, + }, + { + name: "unsupported_attribute_value", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{}, + Attributes: []filterconfig.Attribute{ + { + Key: "keyMap", + Value: "123", + }, + }, + }, + }, + { + name: "property_key_does_not_exist", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Attributes: []filterconfig.Attribute{ + { + Key: "doesnotexist", + Value: nil, + }, + }, + }, + }, + } + + atts := pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + "keyInt": pdata.NewAttributeValueInt(123), + "keyMap": pdata.NewAttributeValueMap(), + }) + + library := pdata.NewInstrumentationLibrary() + library.SetName("lib") + library.SetVersion("ver") + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + matcher, err := NewMatcher(tc.properties) + require.NoError(t, err) + assert.NotNil(t, matcher) + + assert.False(t, matcher.Match(atts, resource("wrongSvc"), library)) + }) + } +} + +func Test_MatchingCornerCases(t *testing.T) { + cfg := &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Attributes: []filterconfig.Attribute{ + { + Key: "keyOne", + Value: nil, + }, + }, + } + + mp, err := NewMatcher(cfg) + assert.Nil(t, err) + assert.NotNil(t, mp) + + assert.False(t, mp.Match(pdata.NewAttributeMap(), resource("svcA"), pdata.NewInstrumentationLibrary())) +} + +func Test_Matching_True(t *testing.T) { + ver := "v.*" + + testcases := []struct { + name string + properties *filterconfig.MatchProperties + }{ + { + name: "library_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Libraries: []filterconfig.InstrumentationLibrary{{Name: "li.*"}}, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "library_match_with_version", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Libraries: []filterconfig.InstrumentationLibrary{{Name: "li.*", Version: &ver}}, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "attribute_exact_value_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{}, + Attributes: []filterconfig.Attribute{ + { + Key: "keyString", + Value: "arithmetic", + }, + { + Key: "keyInt", + Value: 123, + }, + { + Key: "keyDouble", + Value: 3245.6, + }, + { + Key: "keyBool", + Value: true, + }, + }, + }, + }, + { + name: "attribute_regex_value_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Attributes: []filterconfig.Attribute{ + { + Key: "keyString", + Value: "arith.*", + }, + { + Key: "keyInt", + Value: "12.*", + }, + { + Key: "keyDouble", + Value: "324.*", + }, + { + Key: "keyBool", + Value: "tr.*", + }, + }, + }, + }, + { + name: "resource_exact_value_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Resources: []filterconfig.Attribute{ + { + Key: "resString", + Value: "arithmetic", + }, + }, + }, + }, + { + name: "property_exists", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"svcA"}, + Attributes: []filterconfig.Attribute{ + { + Key: "keyExists", + Value: nil, + }, + }, + }, + }, + { + name: "match_all_settings_exists", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"svcA"}, + Attributes: []filterconfig.Attribute{ + { + Key: "keyExists", + Value: nil, + }, + { + Key: "keyString", + Value: "arithmetic", + }, + }, + }, + }, + } + + atts := pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + "keyString": pdata.NewAttributeValueString("arithmetic"), + "keyInt": pdata.NewAttributeValueInt(123), + "keyDouble": pdata.NewAttributeValueDouble(3245.6), + "keyBool": pdata.NewAttributeValueBool(true), + "keyExists": pdata.NewAttributeValueString("present"), + }) + + resource := pdata.NewResource() + resource.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + conventions.AttributeServiceName: pdata.NewAttributeValueString("svcA"), + "resString": pdata.NewAttributeValueString("arithmetic"), + }) + + library := pdata.NewInstrumentationLibrary() + library.SetName("lib") + library.SetVersion("ver") + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + mp, err := NewMatcher(tc.properties) + require.NoError(t, err) + assert.NotNil(t, mp) + + assert.True(t, mp.Match(atts, resource, library)) + }) + } +} + +func resource(service string) pdata.Resource { + r := pdata.NewResource() + r.Attributes().InitFromMap(map[string]pdata.AttributeValue{conventions.AttributeServiceName: pdata.NewAttributeValueString(service)}) + return r +} diff --git a/internal/otel_collector/internal/processor/filtermetric/config.go b/internal/otel_collector/internal/processor/filtermetric/config.go new file mode 100644 index 00000000000..5e164a73a0d --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/config.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/internal/processor/filterset/regexp" +) + +// MatchType specifies the strategy for matching against `pdata.Metric`s. This +// is distinct from filterset.MatchType which matches against metric (and +// tracing) names only. To support matching against metric names and +// `pdata.Metric`s, filtermetric.MatchType is effectively a superset of +// filterset.MatchType. +type MatchType string + +// These are the MatchTypes that users can specify for filtering +// `pdata.Metric`s. +const ( + Regexp = MatchType(filterset.Regexp) + Strict = MatchType(filterset.Strict) + Expr MatchType = "expr" +) + +// MatchProperties specifies the set of properties in a metric to match against and the +// type of string pattern matching to use. +type MatchProperties struct { + // MatchType specifies the type of matching desired + MatchType MatchType `mapstructure:"match_type"` + // RegexpConfig specifies options for the Regexp match type + RegexpConfig *regexp.Config `mapstructure:"regexp"` + + // MetricNames specifies the list of string patterns to match metric names against. + // A match occurs if the metric name matches at least one string pattern in this list. + MetricNames []string `mapstructure:"metric_names"` + + // Expressions specifies the list of expr expressions to match metrics against. + // A match occurs if any datapoint in a metric matches at least one expression in this list. + Expressions []string `mapstructure:"expressions"` +} diff --git a/internal/otel_collector/internal/processor/filtermetric/config_test.go b/internal/otel_collector/internal/processor/filtermetric/config_test.go new file mode 100644 index 00000000000..2c28d5d5753 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/config_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/internal/processor/filterset/regexp" +) + +var ( + // regexpNameMatches matches the metrics names specified in testdata/config.yaml + regexpNameMatches = []string{ + "prefix/.*", + ".*contains.*", + ".*_suffix", + "full_name_match", + } + + strictNameMatches = []string{ + "exact_string_match", + } +) + +func createConfigWithRegexpOptions(filters []string, rCfg *regexp.Config) *MatchProperties { + cfg := createConfig(filters, filterset.Regexp) + cfg.RegexpConfig = rCfg + return cfg +} + +func TestConfig(t *testing.T) { + testFile := path.Join(".", "testdata", "config.yaml") + v := configtest.NewViperFromYamlFile(t, testFile) + + testYamls := map[string]MatchProperties{} + require.NoErrorf(t, v.UnmarshalExact(&testYamls), "unable to unmarshal yaml from file %v", testFile) + + tests := []struct { + name string + expCfg *MatchProperties + }{ + { + name: "config/regexp", + expCfg: createConfig(regexpNameMatches, filterset.Regexp), + }, { + name: "config/regexpoptions", + expCfg: createConfigWithRegexpOptions( + regexpNameMatches, + ®exp.Config{ + CacheEnabled: true, + CacheMaxNumEntries: 5, + }, + ), + }, { + name: "config/strict", + expCfg: createConfig(strictNameMatches, filterset.Strict), + }, { + name: "config/emptyproperties", + expCfg: createConfig(nil, filterset.Regexp), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg := testYamls[test.name] + assert.Equal(t, *test.expCfg, cfg) + + matcher, err := NewMatcher(&cfg) + assert.NotNil(t, matcher) + assert.NoError(t, err) + }) + } +} diff --git a/internal/otel_collector/internal/processor/filtermetric/doc.go b/internal/otel_collector/internal/processor/filtermetric/doc.go new file mode 100644 index 00000000000..29a0f50b1c8 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/doc.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package filtermetric is a helper package for processing metrics. +package filtermetric diff --git a/internal/otel_collector/internal/processor/filtermetric/expr_matcher.go b/internal/otel_collector/internal/processor/filtermetric/expr_matcher.go new file mode 100644 index 00000000000..50352293d88 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/expr_matcher.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterexpr" +) + +type exprMatcher struct { + matchers []*filterexpr.Matcher +} + +func newExprMatcher(expressions []string) (*exprMatcher, error) { + m := &exprMatcher{} + for _, expression := range expressions { + matcher, err := filterexpr.NewMatcher(expression) + if err != nil { + return nil, err + } + m.matchers = append(m.matchers, matcher) + } + return m, nil +} + +func (m *exprMatcher) MatchMetric(metric pdata.Metric) (bool, error) { + for _, matcher := range m.matchers { + matched, err := matcher.MatchMetric(metric) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + return false, nil +} diff --git a/internal/otel_collector/internal/processor/filtermetric/filtermetric.go b/internal/otel_collector/internal/processor/filtermetric/filtermetric.go new file mode 100644 index 00000000000..ec9c176ccf7 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/filtermetric.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +type Matcher interface { + MatchMetric(metric pdata.Metric) (bool, error) +} + +// NewMatcher constructs a metric Matcher. If an 'expr' match type is specified, +// returns an expr matcher, otherwise a name matcher. +func NewMatcher(config *MatchProperties) (Matcher, error) { + if config.MatchType == Expr { + return newExprMatcher(config.Expressions) + } + return newNameMatcher(config) +} diff --git a/internal/otel_collector/internal/processor/filtermetric/filtermetric_test.go b/internal/otel_collector/internal/processor/filtermetric/filtermetric_test.go new file mode 100644 index 00000000000..cdc82af1765 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/filtermetric_test.go @@ -0,0 +1,97 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +var ( + regexpFilters = []string{ + "prefix/.*", + "prefix_.*", + ".*/suffix", + ".*_suffix", + ".*/contains/.*", + ".*_contains_.*", + "full/name/match", + "full_name_match", + } + + strictFilters = []string{ + "exact_string_match", + ".*/suffix", + "(a|b)", + } +) + +func createMetric(name string) pdata.Metric { + metric := pdata.NewMetric() + metric.SetName(name) + return metric +} + +func TestMatcherMatches(t *testing.T) { + tests := []struct { + name string + cfg *MatchProperties + metric pdata.Metric + shouldMatch bool + }{ + { + name: "regexpNameMatch", + cfg: createConfig(regexpFilters, filterset.Regexp), + metric: createMetric("test/match/suffix"), + shouldMatch: true, + }, { + name: "regexpNameMisatch", + cfg: createConfig(regexpFilters, filterset.Regexp), + metric: createMetric("test/match/wrongsuffix"), + shouldMatch: false, + }, { + name: "strictNameMatch", + cfg: createConfig(strictFilters, filterset.Strict), + metric: createMetric("exact_string_match"), + shouldMatch: true, + }, { + name: "strictNameMismatch", + cfg: createConfig(regexpFilters, filterset.Regexp), + metric: createMetric("wrong_string_match"), + shouldMatch: false, + }, { + name: "matcherWithNoPropertyFilters", + cfg: createConfig([]string{}, filterset.Strict), + metric: createMetric("metric"), + shouldMatch: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + matcher, err := NewMatcher(test.cfg) + assert.NotNil(t, matcher) + assert.NoError(t, err) + + matches, err := matcher.MatchMetric(test.metric) + assert.NoError(t, err) + assert.Equal(t, test.shouldMatch, matches) + }) + } +} diff --git a/internal/otel_collector/internal/processor/filtermetric/helpers_test.go b/internal/otel_collector/internal/processor/filtermetric/helpers_test.go new file mode 100644 index 00000000000..dc26b89724a --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/helpers_test.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +func createConfig(filters []string, matchType filterset.MatchType) *MatchProperties { + return &MatchProperties{ + MatchType: MatchType(matchType), + MetricNames: filters, + } +} diff --git a/internal/otel_collector/internal/processor/filtermetric/name_matcher.go b/internal/otel_collector/internal/processor/filtermetric/name_matcher.go new file mode 100644 index 00000000000..41b99407827 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/name_matcher.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filtermetric + +import ( + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +// nameMatcher matches metrics by metric properties against prespecified values for each property. +type nameMatcher struct { + nameFilters filterset.FilterSet +} + +func newNameMatcher(config *MatchProperties) (*nameMatcher, error) { + nameFS, err := filterset.CreateFilterSet( + config.MetricNames, + &filterset.Config{ + MatchType: filterset.MatchType(config.MatchType), + RegexpConfig: config.RegexpConfig, + }, + ) + if err != nil { + return nil, err + } + return &nameMatcher{ + nameFilters: nameFS, + }, nil +} + +// MatchMetric matches a metric using the metric properties configured on the nameMatcher. +// A metric only matches if every metric property configured on the nameMatcher is a match. +func (m *nameMatcher) MatchMetric(metric pdata.Metric) (bool, error) { + return m.nameFilters.Matches(metric.Name()), nil +} diff --git a/internal/otel_collector/internal/processor/filtermetric/testdata/config.yaml b/internal/otel_collector/internal/processor/filtermetric/testdata/config.yaml new file mode 100644 index 00000000000..c3a058d58d1 --- /dev/null +++ b/internal/otel_collector/internal/processor/filtermetric/testdata/config.yaml @@ -0,0 +1,24 @@ +# Yaml form of the configuration for matching metrics +# This configuration can be embedded into other component's yamls +# The top level here are just test names and do not represent part of the actual configuration. + +config/regexp: + match_type: regexp + metric_names: [prefix/.*, .*contains.*, .*_suffix, full_name_match] +config/regexpoptions: + match_type: regexp + regexp: + cacheenabled: true + cachemaxnumentries: 5 + metric_names: + - prefix/.* + - .*contains.* + - .*_suffix + - full_name_match +config/strict: + match_type: strict + metric_names: + - exact_string_match +config/emptyproperties: + match_type: regexp + metric_names: \ No newline at end of file diff --git a/internal/otel_collector/internal/processor/filterset/config.go b/internal/otel_collector/internal/processor/filterset/config.go new file mode 100644 index 00000000000..df131f75397 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/config.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterset + +import ( + "fmt" + + "go.opentelemetry.io/collector/internal/processor/filterset/regexp" + "go.opentelemetry.io/collector/internal/processor/filterset/strict" +) + +// MatchType describes the type of pattern matching a FilterSet uses to filter strings. +type MatchType string + +const ( + // Regexp is the FilterType for filtering by regexp string matches. + Regexp MatchType = "regexp" + // Strict is the FilterType for filtering by exact string matches. + Strict MatchType = "strict" + // MatchTypeFieldName is the mapstructure field name for MatchType field. + MatchTypeFieldName = "match_type" +) + +var ( + validMatchTypes = []MatchType{Regexp, Strict} +) + +// Config configures the matching behavior of a FilterSet. +type Config struct { + MatchType MatchType `mapstructure:"match_type"` + RegexpConfig *regexp.Config `mapstructure:"regexp"` +} + +// CreateFilterSet creates a FilterSet from yaml config. +func CreateFilterSet(filters []string, cfg *Config) (FilterSet, error) { + switch cfg.MatchType { + case Regexp: + return regexp.NewFilterSet(filters, cfg.RegexpConfig) + case Strict: + // Strict FilterSets do not have any extra configuration options, so call the constructor directly. + return strict.NewFilterSet(filters) + default: + return nil, fmt.Errorf("unrecognized %v: '%v', valid types are: %v", MatchTypeFieldName, cfg.MatchType, validMatchTypes) + } +} diff --git a/internal/otel_collector/internal/processor/filterset/config_test.go b/internal/otel_collector/internal/processor/filterset/config_test.go new file mode 100644 index 00000000000..0ce9f7c43ed --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/config_test.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterset + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/internal/processor/filterset/regexp" +) + +func readTestdataConfigYamls(t *testing.T, filename string) map[string]*Config { + testFile := path.Join(".", "testdata", filename) + v := configtest.NewViperFromYamlFile(t, testFile) + + cfgs := map[string]*Config{} + require.NoErrorf(t, v.UnmarshalExact(&cfgs), "unable to unmarshal yaml from file %v", testFile) + return cfgs +} + +func TestConfig(t *testing.T) { + actualConfigs := readTestdataConfigYamls(t, "config.yaml") + expectedConfigs := map[string]*Config{ + "regexp/default": { + MatchType: Regexp, + }, + "regexp/emptyoptions": { + MatchType: Regexp, + }, + "regexp/withoptions": { + MatchType: Regexp, + RegexpConfig: ®exp.Config{ + CacheEnabled: false, + CacheMaxNumEntries: 10, + }, + }, + "strict/default": { + MatchType: Strict, + }, + } + + for testName, actualCfg := range actualConfigs { + t.Run(testName, func(t *testing.T) { + expCfg, ok := expectedConfigs[testName] + assert.True(t, ok) + assert.Equal(t, expCfg, actualCfg) + + fs, err := CreateFilterSet([]string{}, actualCfg) + assert.NoError(t, err) + assert.NotNil(t, fs) + }) + } +} + +func TestConfigInvalid(t *testing.T) { + actualConfigs := readTestdataConfigYamls(t, "config_invalid.yaml") + expectedConfigs := map[string]*Config{ + "invalid/matchtype": { + MatchType: "invalid", + }, + } + + for testName, actualCfg := range actualConfigs { + t.Run(testName, func(t *testing.T) { + expCfg, ok := expectedConfigs[testName] + assert.True(t, ok) + assert.Equal(t, expCfg, actualCfg) + + fs, err := CreateFilterSet([]string{}, actualCfg) + assert.NotNil(t, err) + assert.Nil(t, fs) + }) + } +} diff --git a/internal/otel_collector/internal/processor/filterset/doc.go b/internal/otel_collector/internal/processor/filterset/doc.go new file mode 100644 index 00000000000..6dc697bc0cb --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/doc.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package filterset provides an interface for matching strings against a set of string filters. +package filterset diff --git a/internal/otel_collector/internal/processor/filterset/filterset.go b/internal/otel_collector/internal/processor/filterset/filterset.go new file mode 100644 index 00000000000..137c5e5746b --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/filterset.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterset + +// FilterSet is an interface for matching strings against a set of filters. +type FilterSet interface { + // Matches returns true if the given string matches at least one + // of the filters encapsulated by the FilterSet. + Matches(string) bool +} diff --git a/internal/otel_collector/internal/processor/filterset/regexp/config.go b/internal/otel_collector/internal/processor/filterset/regexp/config.go new file mode 100644 index 00000000000..93839162764 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/regexp/config.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package regexp + +// RegexpConfig represents the options for a NewFilterSet. +type Config struct { + // CacheEnabled determines whether match results are LRU cached to make subsequent matches faster. + // Cache size is unlimited unless CacheMaxNumEntries is also specified. + CacheEnabled bool `mapstructure:"cacheenabled"` + // CacheMaxNumEntries is the max number of entries of the LRU cache that stores match results. + // CacheMaxNumEntries is ignored if CacheEnabled is false. + CacheMaxNumEntries int `mapstructure:"cachemaxnumentries"` +} diff --git a/internal/otel_collector/internal/processor/filterset/regexp/config_test.go b/internal/otel_collector/internal/processor/filterset/regexp/config_test.go new file mode 100644 index 00000000000..00465d742b5 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/regexp/config_test.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package regexp + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configtest" +) + +func TestConfig(t *testing.T) { + testFile := path.Join(".", "testdata", "config.yaml") + v := configtest.NewViperFromYamlFile(t, testFile) + + actualConfigs := map[string]*Config{} + require.NoErrorf(t, v.UnmarshalExact(&actualConfigs), "unable to unmarshal yaml from file %v", testFile) + + expectedConfigs := map[string]*Config{ + "regexp/default": {}, + "regexp/cachedisabledwithsize": { + CacheEnabled: false, + CacheMaxNumEntries: 10, + }, + "regexp/cacheenablednosize": { + CacheEnabled: true, + }, + } + + for testName, actualCfg := range actualConfigs { + t.Run(testName, func(t *testing.T) { + expCfg, ok := expectedConfigs[testName] + assert.True(t, ok) + assert.Equal(t, expCfg, actualCfg) + + fs, err := NewFilterSet([]string{}, actualCfg) + assert.NoError(t, err) + assert.NotNil(t, fs) + }) + } +} diff --git a/internal/otel_collector/internal/processor/filterset/regexp/doc.go b/internal/otel_collector/internal/processor/filterset/regexp/doc.go new file mode 100644 index 00000000000..4c8ab33f066 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/regexp/doc.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package regexp provides an implementation to match strings against a set of regexp string filters. +package regexp diff --git a/internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset.go b/internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset.go new file mode 100644 index 00000000000..d6ced6b00e3 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package regexp + +import ( + "regexp" + + "github.com/golang/groupcache/lru" +) + +// FilterSet encapsulates a set of filters and caches match results. +// Filters are re2 regex strings. +// FilterSet is exported for convenience, but has unexported fields and should be constructed through NewFilterSet. +// +// FilterSet satisfies the FilterSet interface from +// "go.opentelemetry.io/collector/internal/processor/filterset" +type FilterSet struct { + regexes []*regexp.Regexp + cacheEnabled bool + cache *lru.Cache +} + +// NewFilterSet constructs a FilterSet of re2 regex strings. +// If any of the given filters fail to compile into re2, an error is returned. +func NewFilterSet(filters []string, cfg *Config) (*FilterSet, error) { + fs := &FilterSet{ + regexes: make([]*regexp.Regexp, 0, len(filters)), + } + + if cfg != nil && cfg.CacheEnabled { + fs.cacheEnabled = true + fs.cache = lru.New(cfg.CacheMaxNumEntries) + } + + if err := fs.addFilters(filters); err != nil { + return nil, err + } + + return fs, nil +} + +// Matches returns true if the given string matches any of the FilterSet's filters. +// The given string must be fully matched by at least one filter's re2 regex. +func (rfs *FilterSet) Matches(toMatch string) bool { + if rfs.cacheEnabled { + if v, ok := rfs.cache.Get(toMatch); ok { + return v.(bool) + } + } + + for _, r := range rfs.regexes { + if r.MatchString(toMatch) { + if rfs.cacheEnabled { + rfs.cache.Add(toMatch, true) + } + return true + } + } + + if rfs.cacheEnabled { + rfs.cache.Add(toMatch, false) + } + return false +} + +// addFilters compiles all the given filters and stores them as regexes. +// All regexes are automatically anchored to enforce full string matches. +func (rfs *FilterSet) addFilters(filters []string) error { + dedup := make(map[string]struct{}, len(filters)) + for _, f := range filters { + if _, ok := dedup[f]; ok { + continue + } + + re, err := regexp.Compile(f) + if err != nil { + return err + } + rfs.regexes = append(rfs.regexes, re) + dedup[f] = struct{}{} + } + + return nil +} diff --git a/internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset_test.go b/internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset_test.go new file mode 100644 index 00000000000..de9bae05d81 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/regexp/regexpfilterset_test.go @@ -0,0 +1,208 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package regexp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + validRegexpFilters = []string{ + "prefix/.*", + "prefix_.*", + ".*/suffix", + ".*_suffix", + ".*/contains/.*", + ".*_contains_.*", + "full/name/match", + "full_name_match", + } +) + +func TestNewRegexpFilterSet(t *testing.T) { + tests := []struct { + name string + filters []string + success bool + }{ + { + name: "validFilters", + filters: validRegexpFilters, + success: true, + }, { + name: "invalidFilter", + filters: []string{ + "exact_string_match", + "(a|b))", // invalid regex + }, + success: false, + }, { + name: "emptyFilter", + filters: []string{}, + success: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fs, err := NewFilterSet(test.filters, nil) + assert.Equal(t, test.success, fs != nil) + assert.Equal(t, test.success, err == nil) + + if err == nil { + // sanity call + fs.Matches("test") + } + }) + } +} + +func TestRegexpMatches(t *testing.T) { + fs, err := NewFilterSet(validRegexpFilters, &Config{}) + assert.NotNil(t, fs) + assert.NoError(t, err) + assert.False(t, fs.cacheEnabled) + + matches := []string{ + "full/name/match", + "extra/full/name/match/extra", + "full_name_match", + "prefix/test/match", + "prefix_test_match", + "extra/prefix/test/match", + "test/match/suffix", + "test/match/suffixextra", + "test_match_suffix", + "test/contains/match", + "test_contains_match", + } + + for _, m := range matches { + t.Run(m, func(t *testing.T) { + assert.True(t, fs.Matches(m)) + }) + } + + mismatches := []string{ + "not_exact_string_match", + "random", + "c", + } + + for _, m := range mismatches { + t.Run(m, func(t *testing.T) { + assert.False(t, fs.Matches(m)) + }) + } +} + +func TestRegexpDeDup(t *testing.T) { + dupRegexpFilters := []string{ + "prefix/.*", + "prefix/.*", + } + fs, err := NewFilterSet(dupRegexpFilters, &Config{}) + assert.NotNil(t, fs) + assert.NoError(t, err) + assert.False(t, fs.cacheEnabled) + assert.EqualValues(t, 1, len(fs.regexes)) +} + +func TestRegexpMatchesCaches(t *testing.T) { + // 0 means unlimited cache + fs, err := NewFilterSet(validRegexpFilters, &Config{ + CacheEnabled: true, + CacheMaxNumEntries: 0, + }) + assert.NotNil(t, fs) + assert.NoError(t, err) + assert.True(t, fs.cacheEnabled) + + matches := []string{ + "full/name/match", + "extra/full/name/match/extra", + "full_name_match", + "prefix/test/match", + "prefix_test_match", + "extra/prefix/test/match", + "test/match/suffix", + "test/match/suffixextra", + "test_match_suffix", + "test/contains/match", + "test_contains_match", + } + + for _, m := range matches { + t.Run(m, func(t *testing.T) { + assert.True(t, fs.Matches(m)) + + matched, ok := fs.cache.Get(m) + assert.True(t, matched.(bool) && ok) + }) + } + + mismatches := []string{ + "not_exact_string_match", + "random", + "c", + } + + for _, m := range mismatches { + t.Run(m, func(t *testing.T) { + assert.False(t, fs.Matches(m)) + + matched, ok := fs.cache.Get(m) + assert.True(t, !matched.(bool) && ok) + }) + } +} + +func TestWithCacheSize(t *testing.T) { + size := 3 + fs, err := NewFilterSet(validRegexpFilters, &Config{ + CacheEnabled: true, + CacheMaxNumEntries: size, + }) + assert.NotNil(t, fs) + assert.NoError(t, err) + + matches := []string{ + "prefix/test/match", + "prefix_test_match", + "test/match/suffix", + } + + // fill cache + for _, m := range matches { + fs.Matches(m) + _, ok := fs.cache.Get(m) + assert.True(t, ok) + } + + // refresh oldest entry + fs.Matches(matches[0]) + + // cause LRU cache eviction + newest := "new" + fs.Matches(newest) + + _, evictedOk := fs.cache.Get(matches[1]) + assert.False(t, evictedOk) + + _, newOk := fs.cache.Get(newest) + assert.True(t, newOk) +} diff --git a/internal/otel_collector/internal/processor/filterset/regexp/testdata/config.yaml b/internal/otel_collector/internal/processor/filterset/regexp/testdata/config.yaml new file mode 100644 index 00000000000..8d838cbe4b6 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/regexp/testdata/config.yaml @@ -0,0 +1,10 @@ +# Yaml form of the configuration for regexp FilterSets +# This configuration can be embedded into other component's yamls +# The top level here are just test names and do not represent part of the actual configuration. + +regexp/default: +regexp/cachedisabledwithsize: + cacheenabled: false + cachemaxnumentries: 10 +regexp/cacheenablednosize: + cacheenabled: true \ No newline at end of file diff --git a/internal/otel_collector/internal/processor/filterset/strict/doc.go b/internal/otel_collector/internal/processor/filterset/strict/doc.go new file mode 100644 index 00000000000..9a069ab9a9c --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/strict/doc.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package strict provides an implementation to match strings against a set of exact match string filters. +package strict diff --git a/internal/otel_collector/internal/processor/filterset/strict/strictfilterset.go b/internal/otel_collector/internal/processor/filterset/strict/strictfilterset.go new file mode 100644 index 00000000000..459f55866ca --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/strict/strictfilterset.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package strict + +// FilterSet encapsulates a set of exact string match filters. +// FilterSet is exported for convenience, but has unexported fields and should be constructed through NewFilterSet. +// +// regexpFilterSet satisfies the FilterSet interface from +// "go.opentelemetry.io/collector/internal/processor/filterset" +type FilterSet struct { + filters map[string]struct{} +} + +// NewFilterSet constructs a FilterSet of exact string matches. +func NewFilterSet(filters []string) (*FilterSet, error) { + fs := &FilterSet{ + filters: make(map[string]struct{}, len(filters)), + } + + fs.addFilters(filters) + return fs, nil +} + +// Matches returns true if the given string matches any of the FilterSet's filters. +func (sfs *FilterSet) Matches(toMatch string) bool { + _, ok := sfs.filters[toMatch] + return ok +} + +// addFilters all the given filters. +func (sfs *FilterSet) addFilters(filters []string) { + for _, f := range filters { + sfs.filters[f] = struct{}{} + } +} diff --git a/internal/otel_collector/internal/processor/filterset/strict/strictfilterset_test.go b/internal/otel_collector/internal/processor/filterset/strict/strictfilterset_test.go new file mode 100644 index 00000000000..0d5a51cc161 --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/strict/strictfilterset_test.go @@ -0,0 +1,83 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package strict + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + validStrictFilters = []string{ + "exact_string_match", + ".*/suffix", + "(a|b)", + } +) + +func TestNewStrictFilterSet(t *testing.T) { + tests := []struct { + name string + filters []string + success bool + }{ + { + name: "validFilters", + filters: validStrictFilters, + success: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fs, err := NewFilterSet(test.filters) + assert.Equal(t, test.success, fs != nil) + assert.Equal(t, test.success, err == nil) + }) + } +} + +func TestStrictMatches(t *testing.T) { + fs, err := NewFilterSet(validStrictFilters) + assert.NotNil(t, fs) + assert.NoError(t, err) + + matches := []string{ + "exact_string_match", + ".*/suffix", + "(a|b)", + } + + for _, m := range matches { + t.Run(m, func(t *testing.T) { + assert.True(t, fs.Matches(m)) + }) + } + + mismatches := []string{ + "not_exact_string_match", + "random", + "test/match/suffix", + "prefix/metric/one", + "c", + } + + for _, m := range mismatches { + t.Run(m, func(t *testing.T) { + assert.False(t, fs.Matches(m)) + }) + } +} diff --git a/internal/otel_collector/internal/processor/filterset/testdata/config.yaml b/internal/otel_collector/internal/processor/filterset/testdata/config.yaml new file mode 100644 index 00000000000..6fe12bd442c --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/testdata/config.yaml @@ -0,0 +1,16 @@ +# Yaml form of the configuration for FilterSets +# This configuration can be embedded into other component's yamls +# The top level here are just test names and do not represent part of the actual configuration. + +regexp/default: + match_type: regexp +regexp/emptyoptions: + match_type: regexp + regexp: +regexp/withoptions: + match_type: regexp + regexp: + cacheenabled: false + cachemaxnumentries: 10 +strict/default: + match_type: strict \ No newline at end of file diff --git a/internal/otel_collector/internal/processor/filterset/testdata/config_invalid.yaml b/internal/otel_collector/internal/processor/filterset/testdata/config_invalid.yaml new file mode 100644 index 00000000000..bc854ccc63a --- /dev/null +++ b/internal/otel_collector/internal/processor/filterset/testdata/config_invalid.yaml @@ -0,0 +1,6 @@ +# Yaml form of the configuration for FilterSets +# This configuration can be embedded into other component's yamls +# The top level here are just test names and do not represent part of the actual configuration. + +invalid/matchtype: + match_type: "invalid" \ No newline at end of file diff --git a/internal/otel_collector/internal/processor/filterspan/filterspan.go b/internal/otel_collector/internal/processor/filterspan/filterspan.go new file mode 100644 index 00000000000..f4686c35fee --- /dev/null +++ b/internal/otel_collector/internal/processor/filterspan/filterspan.go @@ -0,0 +1,134 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterspan + +import ( + "fmt" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filtermatcher" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/translator/conventions" +) + +// TODO: Modify Matcher to invoke both the include and exclude properties so +// calling processors will always have the same logic. +// Matcher is an interface that allows matching a span against a configuration +// of a match. +type Matcher interface { + MatchSpan(span pdata.Span, resource pdata.Resource, library pdata.InstrumentationLibrary) bool +} + +// propertiesMatcher allows matching a span against various span properties. +type propertiesMatcher struct { + filtermatcher.PropertiesMatcher + + // Service names to compare to. + serviceFilters filterset.FilterSet + + // Span names to compare to. + nameFilters filterset.FilterSet +} + +// NewMatcher creates a span Matcher that matches based on the given MatchProperties. +func NewMatcher(mp *filterconfig.MatchProperties) (Matcher, error) { + if mp == nil { + return nil, nil + } + + if err := mp.ValidateForSpans(); err != nil { + return nil, err + } + + rm, err := filtermatcher.NewMatcher(mp) + if err != nil { + return nil, err + } + + var serviceFS filterset.FilterSet = nil + if len(mp.Services) > 0 { + serviceFS, err = filterset.CreateFilterSet(mp.Services, &mp.Config) + if err != nil { + return nil, fmt.Errorf("error creating service name filters: %v", err) + } + } + + var nameFS filterset.FilterSet = nil + if len(mp.SpanNames) > 0 { + nameFS, err = filterset.CreateFilterSet(mp.SpanNames, &mp.Config) + if err != nil { + return nil, fmt.Errorf("error creating span name filters: %v", err) + } + } + + return &propertiesMatcher{ + PropertiesMatcher: rm, + serviceFilters: serviceFS, + nameFilters: nameFS, + }, nil +} + +// SkipSpan determines if a span should be processed. +// True is returned when a span should be skipped. +// False is returned when a span should not be skipped. +// The logic determining if a span should be processed is set +// in the attribute configuration with the include and exclude settings. +// Include properties are checked before exclude settings are checked. +func SkipSpan(include Matcher, exclude Matcher, span pdata.Span, resource pdata.Resource, library pdata.InstrumentationLibrary) bool { + if include != nil { + // A false returned in this case means the span should not be processed. + if i := include.MatchSpan(span, resource, library); !i { + return true + } + } + + if exclude != nil { + // A true returned in this case means the span should not be processed. + if e := exclude.MatchSpan(span, resource, library); e { + return true + } + } + + return false +} + +// MatchSpan matches a span and service to a set of properties. +// see filterconfig.MatchProperties for more details +func (mp *propertiesMatcher) MatchSpan(span pdata.Span, resource pdata.Resource, library pdata.InstrumentationLibrary) bool { + // If a set of properties was not in the mp, all spans are considered to match on that property + if mp.serviceFilters != nil { + serviceName := serviceNameForResource(resource) + if !mp.serviceFilters.Matches(serviceName) { + return false + } + } + + if mp.nameFilters != nil && !mp.nameFilters.Matches(span.Name()) { + return false + } + + return mp.PropertiesMatcher.Match(span.Attributes(), resource, library) +} + +// serviceNameForResource gets the service name for a specified Resource. +func serviceNameForResource(resource pdata.Resource) string { + service, found := resource.Attributes().Get(conventions.AttributeServiceName) + if !found { + return "" + } + + return service.StringVal() +} diff --git a/internal/otel_collector/internal/processor/filterspan/filterspan_test.go b/internal/otel_collector/internal/processor/filterspan/filterspan_test.go new file mode 100644 index 00000000000..5c4cd68fd7a --- /dev/null +++ b/internal/otel_collector/internal/processor/filterspan/filterspan_test.go @@ -0,0 +1,261 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterspan + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func createConfig(matchType filterset.MatchType) *filterset.Config { + return &filterset.Config{ + MatchType: matchType, + } +} + +func TestSpan_validateMatchesConfiguration_InvalidConfig(t *testing.T) { + testcases := []struct { + name string + property filterconfig.MatchProperties + errorString string + }{ + { + name: "empty_property", + property: filterconfig.MatchProperties{}, + errorString: "at least one of \"services\", \"span_names\", \"attributes\", \"libraries\" or \"resources\" field must be specified", + }, + { + name: "empty_service_span_names_and_attributes", + property: filterconfig.MatchProperties{ + Services: []string{}, + }, + errorString: "at least one of \"services\", \"span_names\", \"attributes\", \"libraries\" or \"resources\" field must be specified", + }, + { + name: "log_properties", + property: filterconfig.MatchProperties{ + LogNames: []string{"log"}, + }, + errorString: "log_names should not be specified for trace spans", + }, + { + name: "invalid_match_type", + property: filterconfig.MatchProperties{ + Config: *createConfig("wrong_match_type"), + Services: []string{"abc"}, + }, + errorString: "error creating service name filters: unrecognized match_type: 'wrong_match_type', valid types are: [regexp strict]", + }, + { + name: "missing_match_type", + property: filterconfig.MatchProperties{ + Services: []string{"abc"}, + }, + errorString: "error creating service name filters: unrecognized match_type: '', valid types are: [regexp strict]", + }, + { + name: "invalid_regexp_pattern_service", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{"["}, + }, + errorString: "error creating service name filters: error parsing regexp: missing closing ]: `[`", + }, + { + name: "invalid_regexp_pattern_span", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanNames: []string{"["}, + }, + errorString: "error creating span name filters: error parsing regexp: missing closing ]: `[`", + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + output, err := NewMatcher(&tc.property) + assert.Nil(t, output) + assert.EqualError(t, err, tc.errorString) + }) + } +} + +func TestSpan_Matching_False(t *testing.T) { + testcases := []struct { + name string + properties *filterconfig.MatchProperties + }{ + { + name: "service_name_doesnt_match_regexp", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{"svcA"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + + { + name: "service_name_doesnt_match_strict", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"svcA"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + + { + name: "span_name_doesnt_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanNames: []string{"spanNo.*Name"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + + { + name: "span_name_doesnt_match_any", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanNames: []string{ + "spanNo.*Name", + "non-matching?pattern", + "regular string", + }, + Attributes: []filterconfig.Attribute{}, + }, + }, + } + + span := pdata.NewSpan() + span.SetName("spanName") + library := pdata.NewInstrumentationLibrary() + resource := pdata.NewResource() + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + matcher, err := NewMatcher(tc.properties) + require.NoError(t, err) + assert.NotNil(t, matcher) + + assert.False(t, matcher.MatchSpan(span, resource, library)) + }) + } +} + +func TestSpan_MissingServiceName(t *testing.T) { + cfg := &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{"svcA"}, + } + + mp, err := NewMatcher(cfg) + assert.Nil(t, err) + assert.NotNil(t, mp) + + emptySpan := pdata.NewSpan() + assert.False(t, mp.MatchSpan(emptySpan, pdata.NewResource(), pdata.NewInstrumentationLibrary())) +} + +func TestSpan_Matching_True(t *testing.T) { + testcases := []struct { + name string + properties *filterconfig.MatchProperties + }{ + { + name: "service_name_match_regexp", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{"svcA"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "service_name_match_strict", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"svcA"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "span_name_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanNames: []string{"span.*"}, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "span_name_second_match", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanNames: []string{ + "wrong.*pattern", + "span.*", + "yet another?pattern", + "regularstring", + }, + Attributes: []filterconfig.Attribute{}, + }, + }, + } + + span := pdata.NewSpan() + span.SetName("spanName") + span.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + "keyString": pdata.NewAttributeValueString("arithmetic"), + "keyInt": pdata.NewAttributeValueInt(123), + "keyDouble": pdata.NewAttributeValueDouble(3245.6), + "keyBool": pdata.NewAttributeValueBool(true), + "keyExists": pdata.NewAttributeValueString("present"), + }) + assert.NotNil(t, span) + + resource := pdata.NewResource() + resource.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + conventions.AttributeServiceName: pdata.NewAttributeValueString("svcA"), + }) + + library := pdata.NewInstrumentationLibrary() + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + mp, err := NewMatcher(tc.properties) + require.NoError(t, err) + assert.NotNil(t, mp) + + assert.True(t, mp.MatchSpan(span, resource, library)) + }) + } +} + +func TestServiceNameForResource(t *testing.T) { + td := testdata.GenerateTraceDataOneSpanNoResource() + require.Equal(t, serviceNameForResource(td.ResourceSpans().At(0).Resource()), "") + + td = testdata.GenerateTraceDataOneSpan() + resource := td.ResourceSpans().At(0).Resource() + require.Equal(t, serviceNameForResource(resource), "") + + resource.Attributes().InsertString(conventions.AttributeServiceName, "test-service") + require.Equal(t, serviceNameForResource(resource), "test-service") +} diff --git a/internal/otel_collector/internal/testdata/common.go b/internal/otel_collector/internal/testdata/common.go new file mode 100644 index 00000000000..ae61c3d2039 --- /dev/null +++ b/internal/otel_collector/internal/testdata/common.go @@ -0,0 +1,179 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +var ( + resourceAttributes1 = map[string]pdata.AttributeValue{"resource-attr": pdata.NewAttributeValueString("resource-attr-val-1")} + resourceAttributes2 = map[string]pdata.AttributeValue{"resource-attr": pdata.NewAttributeValueString("resource-attr-val-2")} + spanEventAttributes = map[string]pdata.AttributeValue{"span-event-attr": pdata.NewAttributeValueString("span-event-attr-val")} + spanLinkAttributes = map[string]pdata.AttributeValue{"span-link-attr": pdata.NewAttributeValueString("span-link-attr-val")} + spanAttributes = map[string]pdata.AttributeValue{"span-attr": pdata.NewAttributeValueString("span-attr-val")} +) + +const ( + TestLabelKey = "label" + TestLabelKey1 = "label-1" + TestLabelValue1 = "label-value-1" + TestLabelKey2 = "label-2" + TestLabelValue2 = "label-value-2" + TestLabelKey3 = "label-3" + TestLabelValue3 = "label-value-3" + TestAttachmentKey = "exemplar-attachment" + TestAttachmentValue = "exemplar-attachment-value" +) + +func initResourceAttributes1(dest pdata.AttributeMap) { + dest.InitFromMap(resourceAttributes1) +} + +func generateOtlpResourceAttributes1() []otlpcommon.KeyValue { + return []otlpcommon.KeyValue{ + { + Key: "resource-attr", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "resource-attr-val-1"}}, + }, + } +} + +func initResourceAttributes2(dest pdata.AttributeMap) { + dest.InitFromMap(resourceAttributes2) +} + +func generateOtlpResourceAttributes2() []otlpcommon.KeyValue { + return []otlpcommon.KeyValue{ + { + Key: "resource-attr", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "resource-attr-val-2"}}, + }, + } +} + +func initSpanAttributes(dest pdata.AttributeMap) { + dest.InitFromMap(spanAttributes) +} + +func generateOtlpSpanAttributes() []otlpcommon.KeyValue { + return []otlpcommon.KeyValue{ + { + Key: "span-attr", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "span-attr-val"}}, + }, + } +} + +func initSpanEventAttributes(dest pdata.AttributeMap) { + dest.InitFromMap(spanEventAttributes) +} + +func generateOtlpSpanEventAttributes() []otlpcommon.KeyValue { + return []otlpcommon.KeyValue{ + { + Key: "span-event-attr", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "span-event-attr-val"}}, + }, + } +} + +func initSpanLinkAttributes(dest pdata.AttributeMap) { + dest.InitFromMap(spanLinkAttributes) +} + +func generateOtlpSpanLinkAttributes() []otlpcommon.KeyValue { + return []otlpcommon.KeyValue{ + { + Key: "span-link-attr", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "span-link-attr-val"}}, + }, + } +} + +func initMetricLabels1(dest pdata.StringMap) { + dest.InitFromMap(map[string]string{TestLabelKey1: TestLabelValue1}) +} + +func generateOtlpMetricLabels1() []otlpcommon.StringKeyValue { + return []otlpcommon.StringKeyValue{ + { + Key: TestLabelKey1, + Value: TestLabelValue1, + }, + } +} + +func initMetricLabels12(dest pdata.StringMap) { + dest.InitFromMap(map[string]string{TestLabelKey1: TestLabelValue1, TestLabelKey2: TestLabelValue2}).Sort() +} + +func generateOtlpMetricLabels12() []otlpcommon.StringKeyValue { + return []otlpcommon.StringKeyValue{ + { + Key: TestLabelKey1, + Value: TestLabelValue1, + }, + { + Key: TestLabelKey2, + Value: TestLabelValue2, + }, + } +} + +func initMetricLabels13(dest pdata.StringMap) { + dest.InitFromMap(map[string]string{TestLabelKey1: TestLabelValue1, TestLabelKey3: TestLabelValue3}).Sort() +} + +func generateOtlpMetricLabels13() []otlpcommon.StringKeyValue { + return []otlpcommon.StringKeyValue{ + { + Key: TestLabelKey1, + Value: TestLabelValue1, + }, + { + Key: TestLabelKey3, + Value: TestLabelValue3, + }, + } +} + +func initMetricLabels2(dest pdata.StringMap) { + dest.InitFromMap(map[string]string{TestLabelKey2: TestLabelValue2}) +} + +func generateOtlpMetricLabels2() []otlpcommon.StringKeyValue { + return []otlpcommon.StringKeyValue{ + { + Key: TestLabelKey2, + Value: TestLabelValue2, + }, + } +} + +func initMetricAttachment(dest pdata.StringMap) { + dest.InitFromMap(map[string]string{TestAttachmentKey: TestAttachmentValue}) +} + +func generateOtlpMetricAttachment() []otlpcommon.StringKeyValue { + return []otlpcommon.StringKeyValue{ + { + Key: TestAttachmentKey, + Value: TestAttachmentValue, + }, + } +} diff --git a/internal/otel_collector/internal/testdata/log.go b/internal/otel_collector/internal/testdata/log.go new file mode 100644 index 00000000000..232ef728ee4 --- /dev/null +++ b/internal/otel_collector/internal/testdata/log.go @@ -0,0 +1,318 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + "time" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/data" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +var ( + TestLogTime = time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC) + TestLogTimestamp = pdata.TimestampUnixNano(TestLogTime.UnixNano()) +) + +func GenerateLogDataEmpty() pdata.Logs { + ld := pdata.NewLogs() + return ld +} + +func generateLogOtlpEmpty() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs(nil) +} + +func GenerateLogDataOneEmptyResourceLogs() pdata.Logs { + ld := GenerateLogDataEmpty() + ld.ResourceLogs().Resize(1) + return ld +} + +func generateLogOtlpOneEmptyResourceLogs() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + {}, + } +} + +func GenerateLogDataNoLogRecords() pdata.Logs { + ld := GenerateLogDataOneEmptyResourceLogs() + rs0 := ld.ResourceLogs().At(0) + initResource1(rs0.Resource()) + return ld +} + +func generateLogOtlpNoLogRecords() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + { + Resource: generateOtlpResource1(), + }, + } +} + +func GenerateLogDataOneEmptyLogs() pdata.Logs { + ld := GenerateLogDataNoLogRecords() + rs0 := ld.ResourceLogs().At(0) + rs0.InstrumentationLibraryLogs().Resize(1) + rs0.InstrumentationLibraryLogs().At(0).Logs().Resize(1) + return ld +} + +func generateLogOtlpOneEmptyLogs() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{ + {}, + }, + }, + }, + }, + } +} + +func GenerateLogDataOneLogNoResource() pdata.Logs { + ld := GenerateLogDataOneEmptyResourceLogs() + rs0 := ld.ResourceLogs().At(0) + rs0.InstrumentationLibraryLogs().Resize(1) + rs0.InstrumentationLibraryLogs().At(0).Logs().Resize(1) + rs0lr0 := rs0.InstrumentationLibraryLogs().At(0).Logs().At(0) + fillLogOne(rs0lr0) + return ld +} + +func generateLogOtlpOneLogNoResource() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + { + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{ + generateOtlpLogOne(), + }, + }, + }, + }, + } +} + +func GenerateLogDataOneLog() pdata.Logs { + ld := GenerateLogDataOneEmptyLogs() + rs0 := ld.ResourceLogs().At(0) + rs0.InstrumentationLibraryLogs().Resize(1) + rs0.InstrumentationLibraryLogs().At(0).Logs().Resize(1) + rs0lr0 := rs0.InstrumentationLibraryLogs().At(0).Logs().At(0) + fillLogOne(rs0lr0) + return ld +} + +func generateLogOtlpOneLog() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{ + generateOtlpLogOne(), + }, + }, + }, + }, + } +} + +func GenerateLogDataTwoLogsSameResource() pdata.Logs { + ld := GenerateLogDataOneEmptyLogs() + rs0 := ld.ResourceLogs().At(0) + rs0.InstrumentationLibraryLogs().Resize(1) + rs0.InstrumentationLibraryLogs().At(0).Logs().Resize(2) + fillLogOne(rs0.InstrumentationLibraryLogs().At(0).Logs().At(0)) + fillLogTwo(rs0.InstrumentationLibraryLogs().At(0).Logs().At(1)) + return ld +} + +// GenerateLogOtlpSameResourceTwologs returns the OTLP representation of the GenerateLogOtlpSameResourceTwologs. +func GenerateLogOtlpSameResourceTwoLogs() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{ + generateOtlpLogOne(), + generateOtlpLogTwo(), + }, + }, + }, + }, + } +} + +func GenerateLogDataTwoLogsSameResourceOneDifferent() pdata.Logs { + ld := pdata.NewLogs() + ld.ResourceLogs().Resize(2) + rl0 := ld.ResourceLogs().At(0) + initResource1(rl0.Resource()) + rl0.InstrumentationLibraryLogs().Resize(1) + rl0.InstrumentationLibraryLogs().At(0).Logs().Resize(2) + fillLogOne(rl0.InstrumentationLibraryLogs().At(0).Logs().At(0)) + fillLogTwo(rl0.InstrumentationLibraryLogs().At(0).Logs().At(1)) + rl1 := ld.ResourceLogs().At(1) + initResource2(rl1.Resource()) + rl1.InstrumentationLibraryLogs().Resize(1) + rl1.InstrumentationLibraryLogs().At(0).Logs().Resize(1) + fillLogThree(rl1.InstrumentationLibraryLogs().At(0).Logs().At(0)) + return ld +} + +func generateLogOtlpTwoLogsSameResourceOneDifferent() []*otlplogs.ResourceLogs { + return []*otlplogs.ResourceLogs{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{ + generateOtlpLogOne(), + generateOtlpLogTwo(), + }, + }, + }, + }, + { + Resource: generateOtlpResource2(), + InstrumentationLibraryLogs: []*otlplogs.InstrumentationLibraryLogs{ + { + Logs: []*otlplogs.LogRecord{ + generateOtlpLogThree(), + }, + }, + }, + }, + } +} + +func fillLogOne(log pdata.LogRecord) { + log.SetName("logA") + log.SetTimestamp(TestLogTimestamp) + log.SetDroppedAttributesCount(1) + log.SetSeverityNumber(pdata.SeverityNumberINFO) + log.SetSeverityText("Info") + log.SetSpanID(pdata.NewSpanID([8]byte{0x01, 0x02, 0x04, 0x08})) + log.SetTraceID(pdata.NewTraceID([16]byte{0x08, 0x04, 0x02, 0x01})) + + attrs := log.Attributes() + attrs.InsertString("app", "server") + attrs.InsertInt("instance_num", 1) + + log.Body().SetStringVal("This is a log message") +} + +func generateOtlpLogOne() *otlplogs.LogRecord { + return &otlplogs.LogRecord{ + Name: "logA", + TimeUnixNano: uint64(TestLogTimestamp), + DroppedAttributesCount: 1, + SeverityNumber: otlplogs.SeverityNumber_SEVERITY_NUMBER_INFO, + SeverityText: "Info", + Body: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "This is a log message"}}, + SpanId: data.NewSpanID([8]byte{0x01, 0x02, 0x04, 0x08}), + TraceId: data.NewTraceID([16]byte{0x08, 0x04, 0x02, 0x01}), + Attributes: []otlpcommon.KeyValue{ + { + Key: "app", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "server"}}, + }, + { + Key: "instance_num", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_IntValue{IntValue: 1}}, + }, + }, + } +} + +func fillLogTwo(log pdata.LogRecord) { + log.SetName("logB") + log.SetTimestamp(TestLogTimestamp) + log.SetDroppedAttributesCount(1) + log.SetSeverityNumber(pdata.SeverityNumberINFO) + log.SetSeverityText("Info") + + attrs := log.Attributes() + attrs.InsertString("customer", "acme") + attrs.InsertString("env", "dev") + + log.Body().SetStringVal("something happened") +} + +func generateOtlpLogTwo() *otlplogs.LogRecord { + return &otlplogs.LogRecord{ + Name: "logB", + TimeUnixNano: uint64(TestLogTimestamp), + DroppedAttributesCount: 1, + SeverityNumber: otlplogs.SeverityNumber_SEVERITY_NUMBER_INFO, + SeverityText: "Info", + Body: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "something happened"}}, + Attributes: []otlpcommon.KeyValue{ + { + Key: "customer", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "acme"}}, + }, + { + Key: "env", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "dev"}}, + }, + }, + } +} + +func fillLogThree(log pdata.LogRecord) { + log.SetName("logC") + log.SetTimestamp(TestLogTimestamp) + log.SetDroppedAttributesCount(1) + log.SetSeverityNumber(pdata.SeverityNumberWARN) + log.SetSeverityText("Warning") + + log.Body().SetStringVal("something else happened") +} + +func generateOtlpLogThree() *otlplogs.LogRecord { + return &otlplogs.LogRecord{ + Name: "logC", + TimeUnixNano: uint64(TestLogTimestamp), + DroppedAttributesCount: 1, + SeverityNumber: otlplogs.SeverityNumber_SEVERITY_NUMBER_WARN, + SeverityText: "Warning", + Body: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "something else happened"}}, + } +} + +func GenerateLogDataManyLogsSameResource(count int) pdata.Logs { + ld := GenerateLogDataOneEmptyLogs() + rs0 := ld.ResourceLogs().At(0) + rs0.InstrumentationLibraryLogs().Resize(1) + rs0.InstrumentationLibraryLogs().At(0).Logs().Resize(count) + for i := 0; i < count; i++ { + l := rs0.InstrumentationLibraryLogs().At(0).Logs().At(i) + if i%2 == 0 { + fillLogOne(l) + } else { + fillLogTwo(l) + } + } + return ld +} diff --git a/internal/otel_collector/internal/testdata/log_test.go b/internal/otel_collector/internal/testdata/log_test.go new file mode 100644 index 00000000000..2ceb87ede33 --- /dev/null +++ b/internal/otel_collector/internal/testdata/log_test.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal" + otlplogs "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" +) + +type logTestCase struct { + name string + ld pdata.Logs + otlp []*otlplogs.ResourceLogs +} + +func generateAllLogTestCases() []logTestCase { + return []logTestCase{ + { + name: "empty", + ld: GenerateLogDataEmpty(), + otlp: generateLogOtlpEmpty(), + }, + { + name: "one-empty-resource-logs", + ld: GenerateLogDataOneEmptyResourceLogs(), + otlp: generateLogOtlpOneEmptyResourceLogs(), + }, + { + name: "no-log-records", + ld: GenerateLogDataNoLogRecords(), + otlp: generateLogOtlpNoLogRecords(), + }, + { + name: "one-empty-log-record", + ld: GenerateLogDataOneEmptyLogs(), + otlp: generateLogOtlpOneEmptyLogs(), + }, + { + name: "one-log-record-no-resource", + ld: GenerateLogDataOneLogNoResource(), + otlp: generateLogOtlpOneLogNoResource(), + }, + { + name: "one-log-record", + ld: GenerateLogDataOneLog(), + otlp: generateLogOtlpOneLog(), + }, + { + name: "two-records-same-resource", + ld: GenerateLogDataTwoLogsSameResource(), + otlp: GenerateLogOtlpSameResourceTwoLogs(), + }, + { + name: "two-records-same-resource-one-different", + ld: GenerateLogDataTwoLogsSameResourceOneDifferent(), + otlp: generateLogOtlpTwoLogsSameResourceOneDifferent(), + }, + } +} + +func TestToFromOtlpLog(t *testing.T) { + allTestCases := generateAllLogTestCases() + // Ensure NumLogTests gets updated. + for i := range allTestCases { + test := allTestCases[i] + t.Run(test.name, func(t *testing.T) { + ld := pdata.LogsFromInternalRep(internal.LogsFromOtlp(test.otlp)) + assert.EqualValues(t, test.ld, ld) + otlp := internal.LogsToOtlp(ld.InternalRep()) + assert.EqualValues(t, test.otlp, otlp) + }) + } +} diff --git a/internal/otel_collector/internal/testdata/metric.go b/internal/otel_collector/internal/testdata/metric.go new file mode 100644 index 00000000000..4395a5bc312 --- /dev/null +++ b/internal/otel_collector/internal/testdata/metric.go @@ -0,0 +1,635 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + "time" + + "go.opentelemetry.io/collector/consumer/pdata" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +var ( + TestMetricStartTime = time.Date(2020, 2, 11, 20, 26, 12, 321, time.UTC) + TestMetricStartTimestamp = pdata.TimestampUnixNano(TestMetricStartTime.UnixNano()) + + TestMetricExemplarTime = time.Date(2020, 2, 11, 20, 26, 13, 123, time.UTC) + TestMetricExemplarTimestamp = pdata.TimestampUnixNano(TestMetricExemplarTime.UnixNano()) + + TestMetricTime = time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC) + TestMetricTimestamp = pdata.TimestampUnixNano(TestMetricTime.UnixNano()) +) + +const ( + TestGaugeDoubleMetricName = "gauge-double" + TestGaugeIntMetricName = "gauge-int" + TestCounterDoubleMetricName = "counter-double" + TestCounterIntMetricName = "counter-int" + TestDoubleHistogramMetricName = "double-histogram" + TestIntHistogramMetricName = "int-histogram" + TestDoubleSummaryMetricName = "double-summary" +) + +func GenerateMetricsEmpty() pdata.Metrics { + md := pdata.NewMetrics() + return md +} + +func generateMetricsOtlpEmpty() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics(nil) +} + +func GenerateMetricsOneEmptyResourceMetrics() pdata.Metrics { + md := GenerateMetricsEmpty() + md.ResourceMetrics().Resize(1) + return md +} + +func generateMetricsOtlpOneEmptyResourceMetrics() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + {}, + } +} + +func GenerateMetricsNoLibraries() pdata.Metrics { + md := GenerateMetricsOneEmptyResourceMetrics() + ms0 := md.ResourceMetrics().At(0) + initResource1(ms0.Resource()) + return md +} + +func generateMetricsOtlpNoLibraries() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + Resource: generateOtlpResource1(), + }, + } +} + +func GenerateMetricsOneEmptyInstrumentationLibrary() pdata.Metrics { + md := GenerateMetricsNoLibraries() + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().Resize(1) + return md +} + +// generateMetricsOtlpOneEmptyInstrumentationLibrary returns the OTLP representation of the GenerateMetricsOneEmptyInstrumentationLibrary. +func generateMetricsOtlpOneEmptyInstrumentationLibrary() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + {}, + }, + }, + } +} + +func GenerateMetricsOneMetricNoResource() pdata.Metrics { + md := GenerateMetricsOneEmptyResourceMetrics() + rm0 := md.ResourceMetrics().At(0) + rm0.InstrumentationLibraryMetrics().Resize(1) + rm0ils0 := rm0.InstrumentationLibraryMetrics().At(0) + rm0ils0.Metrics().Resize(1) + initCounterIntMetric(rm0ils0.Metrics().At(0)) + return md +} + +func generateMetricsOtlpOneMetricNoResource() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + generateOtlpCounterIntMetric(), + }, + }, + }, + }, + } +} + +func GenerateMetricsOneMetric() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + rm0ils0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + rm0ils0.Metrics().Resize(1) + initCounterIntMetric(rm0ils0.Metrics().At(0)) + return md +} + +func generateMetricsOtlpOneMetric() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + generateOtlpCounterIntMetric(), + }, + }, + }, + }, + } +} + +func GenerateMetricsOneMetricOneDataPoint() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + rm0ils0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + rm0ils0.Metrics().Resize(1) + initGaugeIntMetricOneDataPoint(rm0ils0.Metrics().At(0)) + return md +} + +func GenerateMetricsTwoMetrics() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + rm0ils0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + rm0ils0.Metrics().Resize(2) + initCounterIntMetric(rm0ils0.Metrics().At(0)) + initCounterIntMetric(rm0ils0.Metrics().At(1)) + return md +} + +func GenerateMetricsOneCounterOneSummaryMetrics() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + rm0ils0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + rm0ils0.Metrics().Resize(2) + initCounterIntMetric(rm0ils0.Metrics().At(0)) + initDoubleSummaryMetric(rm0ils0.Metrics().At(1)) + return md +} + +func GenerateMetricsOtlpTwoMetrics() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + generateOtlpCounterIntMetric(), + generateOtlpCounterIntMetric(), + }, + }, + }, + }, + } +} + +func GenerateMetricsOneMetricNoLabels() pdata.Metrics { + md := GenerateMetricsOneMetric() + dps := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0).IntSum().DataPoints() + dps.At(0).LabelsMap().InitFromMap(map[string]string{}) + dps.At(1).LabelsMap().InitFromMap(map[string]string{}) + return md +} + +func generateMetricsOtlpOneMetricNoLabels() []*otlpmetrics.ResourceMetrics { + md := generateMetricsOtlpOneMetric() + mis := md[0].InstrumentationLibraryMetrics[0].Metrics[0].Data.(*otlpmetrics.Metric_IntSum).IntSum + mis.DataPoints[0].Labels = nil + mis.DataPoints[1].Labels = nil + return md +} + +func GenerateMetricsAllTypesNoDataPoints() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + ilm0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + ms := ilm0.Metrics() + ms.Resize(7) + initMetric(ms.At(0), TestGaugeDoubleMetricName, pdata.MetricDataTypeDoubleGauge) + initMetric(ms.At(1), TestGaugeIntMetricName, pdata.MetricDataTypeIntGauge) + initMetric(ms.At(2), TestCounterDoubleMetricName, pdata.MetricDataTypeDoubleSum) + initMetric(ms.At(3), TestCounterIntMetricName, pdata.MetricDataTypeIntSum) + initMetric(ms.At(4), TestDoubleHistogramMetricName, pdata.MetricDataTypeDoubleHistogram) + initMetric(ms.At(5), TestIntHistogramMetricName, pdata.MetricDataTypeIntHistogram) + initMetric(ms.At(6), TestDoubleSummaryMetricName, pdata.MetricDataTypeDoubleSummary) + return md +} + +func GenerateMetricsAllTypesEmptyDataPoint() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + ilm0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + ms := ilm0.Metrics() + ms.Resize(7) + + initMetric(ms.At(0), TestGaugeDoubleMetricName, pdata.MetricDataTypeDoubleGauge) + ms.At(0).DoubleGauge().DataPoints().Resize(1) + initMetric(ms.At(1), TestGaugeIntMetricName, pdata.MetricDataTypeIntGauge) + ms.At(1).IntGauge().DataPoints().Resize(1) + initMetric(ms.At(2), TestCounterDoubleMetricName, pdata.MetricDataTypeDoubleSum) + ms.At(2).DoubleSum().DataPoints().Resize(1) + initMetric(ms.At(3), TestCounterIntMetricName, pdata.MetricDataTypeIntSum) + ms.At(3).IntSum().DataPoints().Resize(1) + initMetric(ms.At(4), TestDoubleHistogramMetricName, pdata.MetricDataTypeDoubleHistogram) + ms.At(4).DoubleHistogram().DataPoints().Resize(1) + initMetric(ms.At(5), TestIntHistogramMetricName, pdata.MetricDataTypeIntHistogram) + ms.At(5).IntHistogram().DataPoints().Resize(1) + initMetric(ms.At(6), TestDoubleSummaryMetricName, pdata.MetricDataTypeDoubleSummary) + ms.At(6).DoubleSummary().DataPoints().Resize(1) + return md +} + +func GenerateMetricsMetricTypeInvalid() pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + ilm0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + ms := ilm0.Metrics() + ms.Resize(1) + + initMetric(ms.At(0), TestCounterIntMetricName, pdata.MetricDataTypeNone) + return md +} + +func generateMetricsOtlpAllTypesNoDataPoints() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + generateOtlpMetric(TestGaugeDoubleMetricName, pdata.MetricDataTypeDoubleGauge), + generateOtlpMetric(TestGaugeIntMetricName, pdata.MetricDataTypeIntGauge), + generateOtlpMetric(TestCounterDoubleMetricName, pdata.MetricDataTypeDoubleSum), + generateOtlpMetric(TestCounterIntMetricName, pdata.MetricDataTypeIntSum), + generateOtlpMetric(TestDoubleHistogramMetricName, pdata.MetricDataTypeDoubleHistogram), + generateOtlpMetric(TestIntHistogramMetricName, pdata.MetricDataTypeIntHistogram), + generateOtlpMetric(TestDoubleSummaryMetricName, pdata.MetricDataTypeDoubleSummary), + }, + }, + }, + }, + } +} + +func GeneratMetricsAllTypesWithSampleDatapoints() pdata.Metrics { + metricData := pdata.NewMetrics() + metricData.ResourceMetrics().Resize(1) + + rms := metricData.ResourceMetrics() + initResource1(rms.At(0).Resource()) + rms.At(0).InstrumentationLibraryMetrics().Resize(1) + + ilms := rms.At(0).InstrumentationLibraryMetrics() + ilms.At(0).Metrics().Resize(5) + ms := ilms.At(0).Metrics() + initCounterIntMetric(ms.At(0)) + initSumDoubleMetric(ms.At(1)) + initDoubleHistogramMetric(ms.At(2)) + initIntHistogramMetric(ms.At(3)) + initDoubleSummaryMetric(ms.At(4)) + + return metricData +} + +func generateMetricsOtlpAllTypesWithSampleDatapoints() []*otlpmetrics.ResourceMetrics { + return []*otlpmetrics.ResourceMetrics{ + { + Resource: generateOtlpResource1(), + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + generateOtlpCounterIntMetric(), + generateOtlpSumDoubleMetric(), + generateOtlpDoubleHistogramMetric(), + generateOtlpIntHistogramMetric(), + generateOTLPDoubleSummaryMetric(), + }, + }, + }, + }, + } +} + +func initCounterIntMetric(im pdata.Metric) { + initMetric(im, TestCounterIntMetricName, pdata.MetricDataTypeIntSum) + + idps := im.IntSum().DataPoints() + idps.Resize(2) + idp0 := idps.At(0) + initMetricLabels1(idp0.LabelsMap()) + idp0.SetStartTime(TestMetricStartTimestamp) + idp0.SetTimestamp(TestMetricTimestamp) + idp0.SetValue(123) + idp1 := idps.At(1) + initMetricLabels2(idp1.LabelsMap()) + idp1.SetStartTime(TestMetricStartTimestamp) + idp1.SetTimestamp(TestMetricTimestamp) + idp1.SetValue(456) +} + +func initGaugeIntMetricOneDataPoint(im pdata.Metric) { + initMetric(im, TestGaugeIntMetricName, pdata.MetricDataTypeIntGauge) + + idps := im.IntGauge().DataPoints() + idps.Resize(1) + idp0 := idps.At(0) + initMetricLabels1(idp0.LabelsMap()) + idp0.SetStartTime(TestMetricStartTimestamp) + idp0.SetTimestamp(TestMetricTimestamp) + idp0.SetValue(123) +} + +func generateOtlpCounterIntMetric() *otlpmetrics.Metric { + m := generateOtlpMetric(TestCounterIntMetricName, pdata.MetricDataTypeIntSum) + m.Data.(*otlpmetrics.Metric_IntSum).IntSum.DataPoints = + []*otlpmetrics.IntDataPoint{ + { + Labels: generateOtlpMetricLabels1(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Value: 123, + }, + { + Labels: generateOtlpMetricLabels2(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Value: 456, + }, + } + return m +} + +func initSumDoubleMetric(dm pdata.Metric) { + initMetric(dm, TestCounterDoubleMetricName, pdata.MetricDataTypeDoubleSum) + + ddps := dm.DoubleSum().DataPoints() + ddps.Resize(2) + ddp0 := ddps.At(0) + initMetricLabels12(ddp0.LabelsMap()) + ddp0.SetStartTime(TestMetricStartTimestamp) + ddp0.SetTimestamp(TestMetricTimestamp) + ddp0.SetValue(1.23) + + ddp1 := ddps.At(1) + initMetricLabels13(ddp1.LabelsMap()) + ddp1.SetStartTime(TestMetricStartTimestamp) + ddp1.SetTimestamp(TestMetricTimestamp) + ddp1.SetValue(4.56) +} + +func generateOtlpSumDoubleMetric() *otlpmetrics.Metric { + m := generateOtlpMetric(TestCounterDoubleMetricName, pdata.MetricDataTypeDoubleSum) + m.Data.(*otlpmetrics.Metric_DoubleSum).DoubleSum.DataPoints = + []*otlpmetrics.DoubleDataPoint{ + { + Labels: generateOtlpMetricLabels12(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Value: 1.23, + }, + { + Labels: generateOtlpMetricLabels13(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Value: 4.56, + }, + } + return m +} + +func initDoubleHistogramMetric(hm pdata.Metric) { + initMetric(hm, TestDoubleHistogramMetricName, pdata.MetricDataTypeDoubleHistogram) + + hdps := hm.DoubleHistogram().DataPoints() + hdps.Resize(2) + hdp0 := hdps.At(0) + initMetricLabels13(hdp0.LabelsMap()) + hdp0.SetStartTime(TestMetricStartTimestamp) + hdp0.SetTimestamp(TestMetricTimestamp) + hdp0.SetCount(1) + hdp0.SetSum(15) + hdp1 := hdps.At(1) + initMetricLabels2(hdp1.LabelsMap()) + hdp1.SetStartTime(TestMetricStartTimestamp) + hdp1.SetTimestamp(TestMetricTimestamp) + hdp1.SetCount(1) + hdp1.SetSum(15) + hdp1.SetBucketCounts([]uint64{0, 1}) + exemplars := hdp1.Exemplars() + exemplars.Resize(1) + exemplar := exemplars.At(0) + exemplar.SetTimestamp(TestMetricExemplarTimestamp) + exemplar.SetValue(15) + initMetricAttachment(exemplar.FilteredLabels()) + hdp1.SetExplicitBounds([]float64{1}) +} + +func generateOtlpDoubleHistogramMetric() *otlpmetrics.Metric { + m := generateOtlpMetric(TestDoubleHistogramMetricName, pdata.MetricDataTypeDoubleHistogram) + m.Data.(*otlpmetrics.Metric_DoubleHistogram).DoubleHistogram.DataPoints = + []*otlpmetrics.DoubleHistogramDataPoint{ + { + Labels: generateOtlpMetricLabels13(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Count: 1, + Sum: 15, + }, + { + Labels: generateOtlpMetricLabels2(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Count: 1, + Sum: 15, + BucketCounts: []uint64{0, 1}, + ExplicitBounds: []float64{1}, + Exemplars: []*otlpmetrics.DoubleExemplar{ + { + FilteredLabels: generateOtlpMetricAttachment(), + TimeUnixNano: uint64(TestMetricExemplarTimestamp), + Value: 15, + }, + }, + }, + } + return m +} + +func initIntHistogramMetric(hm pdata.Metric) { + initMetric(hm, TestIntHistogramMetricName, pdata.MetricDataTypeIntHistogram) + + hdps := hm.IntHistogram().DataPoints() + hdps.Resize(2) + hdp0 := hdps.At(0) + initMetricLabels13(hdp0.LabelsMap()) + hdp0.SetStartTime(TestMetricStartTimestamp) + hdp0.SetTimestamp(TestMetricTimestamp) + hdp0.SetCount(1) + hdp0.SetSum(15) + hdp1 := hdps.At(1) + initMetricLabels2(hdp1.LabelsMap()) + hdp1.SetStartTime(TestMetricStartTimestamp) + hdp1.SetTimestamp(TestMetricTimestamp) + hdp1.SetCount(1) + hdp1.SetSum(15) + hdp1.SetBucketCounts([]uint64{0, 1}) + exemplars := hdp1.Exemplars() + exemplars.Resize(1) + exemplar := exemplars.At(0) + exemplar.SetTimestamp(TestMetricExemplarTimestamp) + exemplar.SetValue(15) + initMetricAttachment(exemplar.FilteredLabels()) + hdp1.SetExplicitBounds([]float64{1}) +} + +func generateOtlpIntHistogramMetric() *otlpmetrics.Metric { + m := generateOtlpMetric(TestIntHistogramMetricName, pdata.MetricDataTypeIntHistogram) + m.Data.(*otlpmetrics.Metric_IntHistogram).IntHistogram.DataPoints = + []*otlpmetrics.IntHistogramDataPoint{ + { + Labels: generateOtlpMetricLabels13(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Count: 1, + Sum: 15, + }, + { + Labels: generateOtlpMetricLabels2(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Count: 1, + Sum: 15, + BucketCounts: []uint64{0, 1}, + ExplicitBounds: []float64{1}, + Exemplars: []*otlpmetrics.IntExemplar{ + { + FilteredLabels: generateOtlpMetricAttachment(), + TimeUnixNano: uint64(TestMetricExemplarTimestamp), + Value: 15, + }, + }, + }, + } + return m +} + +func initDoubleSummaryMetric(sm pdata.Metric) { + initMetric(sm, TestDoubleSummaryMetricName, pdata.MetricDataTypeDoubleSummary) + + sdps := sm.DoubleSummary().DataPoints() + sdps.Resize(2) + sdp0 := sdps.At(0) + initMetricLabels13(sdp0.LabelsMap()) + sdp0.SetStartTime(TestMetricStartTimestamp) + sdp0.SetTimestamp(TestMetricTimestamp) + sdp0.SetCount(1) + sdp0.SetSum(15) + sdp1 := sdps.At(1) + initMetricLabels2(sdp1.LabelsMap()) + sdp1.SetStartTime(TestMetricStartTimestamp) + sdp1.SetTimestamp(TestMetricTimestamp) + sdp1.SetCount(1) + sdp1.SetSum(15) + + quantiles := pdata.NewValueAtQuantileSlice() + quantiles.Resize(1) + quantiles.At(0).SetQuantile(0.01) + quantiles.At(0).SetValue(15) + + quantiles.CopyTo(sdp1.QuantileValues()) +} + +func generateOTLPDoubleSummaryMetric() *otlpmetrics.Metric { + m := generateOtlpMetric(TestDoubleSummaryMetricName, pdata.MetricDataTypeDoubleSummary) + m.Data.(*otlpmetrics.Metric_DoubleSummary).DoubleSummary.DataPoints = + []*otlpmetrics.DoubleSummaryDataPoint{ + { + Labels: generateOtlpMetricLabels13(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Count: 1, + Sum: 15, + }, + { + Labels: generateOtlpMetricLabels2(), + StartTimeUnixNano: uint64(TestMetricStartTimestamp), + TimeUnixNano: uint64(TestMetricTimestamp), + Count: 1, + Sum: 15, + QuantileValues: []*otlpmetrics.DoubleSummaryDataPoint_ValueAtQuantile{ + { + Quantile: 0.01, + Value: 15, + }, + }, + }, + } + return m +} + +func initMetric(m pdata.Metric, name string, ty pdata.MetricDataType) { + m.SetName(name) + m.SetDescription("") + m.SetUnit("1") + m.SetDataType(ty) + switch ty { + case pdata.MetricDataTypeIntSum: + sum := m.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + case pdata.MetricDataTypeDoubleSum: + sum := m.DoubleSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + case pdata.MetricDataTypeIntHistogram: + histo := m.IntHistogram() + histo.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + case pdata.MetricDataTypeDoubleHistogram: + histo := m.DoubleHistogram() + histo.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + } +} + +func generateOtlpMetric(name string, ty pdata.MetricDataType) *otlpmetrics.Metric { + m := &otlpmetrics.Metric{ + Name: name, + Description: "", + Unit: "1", + } + switch ty { + case pdata.MetricDataTypeIntGauge: + m.Data = &otlpmetrics.Metric_IntGauge{IntGauge: &otlpmetrics.IntGauge{}} + case pdata.MetricDataTypeDoubleGauge: + m.Data = &otlpmetrics.Metric_DoubleGauge{DoubleGauge: &otlpmetrics.DoubleGauge{}} + case pdata.MetricDataTypeIntSum: + m.Data = &otlpmetrics.Metric_IntSum{IntSum: &otlpmetrics.IntSum{ + IsMonotonic: true, + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + }} + case pdata.MetricDataTypeDoubleSum: + m.Data = &otlpmetrics.Metric_DoubleSum{DoubleSum: &otlpmetrics.DoubleSum{ + IsMonotonic: true, + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + }} + case pdata.MetricDataTypeIntHistogram: + m.Data = &otlpmetrics.Metric_IntHistogram{IntHistogram: &otlpmetrics.IntHistogram{ + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + }} + case pdata.MetricDataTypeDoubleHistogram: + m.Data = &otlpmetrics.Metric_DoubleHistogram{DoubleHistogram: &otlpmetrics.DoubleHistogram{ + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + }} + case pdata.MetricDataTypeDoubleSummary: + m.Data = &otlpmetrics.Metric_DoubleSummary{DoubleSummary: &otlpmetrics.DoubleSummary{}} + } + return m +} + +func GenerateMetricsManyMetricsSameResource(metricsCount int) pdata.Metrics { + md := GenerateMetricsOneEmptyInstrumentationLibrary() + rs0ilm0 := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0) + rs0ilm0.Metrics().Resize(metricsCount) + for i := 0; i < metricsCount; i++ { + initCounterIntMetric(rs0ilm0.Metrics().At(i)) + } + return md +} diff --git a/internal/otel_collector/internal/testdata/metric_test.go b/internal/otel_collector/internal/testdata/metric_test.go new file mode 100644 index 00000000000..b686cb9f98e --- /dev/null +++ b/internal/otel_collector/internal/testdata/metric_test.go @@ -0,0 +1,105 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" +) + +type traceMetricsCase struct { + name string + td pdata.Metrics + otlp []*otlpmetrics.ResourceMetrics +} + +func generateAllMetricsTestCases() []traceMetricsCase { + return []traceMetricsCase{ + { + name: "empty", + td: GenerateMetricsEmpty(), + otlp: generateMetricsOtlpEmpty(), + }, + { + name: "one-empty-resource-metrics", + td: GenerateMetricsOneEmptyResourceMetrics(), + otlp: generateMetricsOtlpOneEmptyResourceMetrics(), + }, + { + name: "no-libraries", + td: GenerateMetricsNoLibraries(), + otlp: generateMetricsOtlpNoLibraries(), + }, + { + name: "one-empty-instrumentation-library", + td: GenerateMetricsOneEmptyInstrumentationLibrary(), + otlp: generateMetricsOtlpOneEmptyInstrumentationLibrary(), + }, + { + name: "one-metric-no-resource", + td: GenerateMetricsOneMetricNoResource(), + otlp: generateMetricsOtlpOneMetricNoResource(), + }, + { + name: "one-metric", + td: GenerateMetricsOneMetric(), + otlp: generateMetricsOtlpOneMetric(), + }, + { + name: "two-metrics", + td: GenerateMetricsTwoMetrics(), + otlp: GenerateMetricsOtlpTwoMetrics(), + }, + { + name: "one-metric-no-labels", + td: GenerateMetricsOneMetricNoLabels(), + otlp: generateMetricsOtlpOneMetricNoLabels(), + }, + { + name: "all-types-no-data-points", + td: GenerateMetricsAllTypesNoDataPoints(), + otlp: generateMetricsOtlpAllTypesNoDataPoints(), + }, + { + name: "all-metric-types", + td: GeneratMetricsAllTypesWithSampleDatapoints(), + otlp: generateMetricsOtlpAllTypesWithSampleDatapoints(), + }, + } +} + +func TestToFromOtlpMetrics(t *testing.T) { + allTestCases := generateAllMetricsTestCases() + // Ensure NumMetricTests gets updated. + for i := range allTestCases { + test := allTestCases[i] + t.Run(test.name, func(t *testing.T) { + td := pdata.MetricsFromOtlp(test.otlp) + assert.EqualValues(t, test.td, td) + otlp := pdata.MetricsToOtlp(td) + assert.EqualValues(t, test.otlp, otlp) + }) + } +} + +func TestGenerateMetricsManyMetricsSameResource(t *testing.T) { + md := GenerateMetricsManyMetricsSameResource(100) + assert.EqualValues(t, 1, md.ResourceMetrics().Len()) + assert.EqualValues(t, 100, md.MetricCount()) +} diff --git a/internal/otel_collector/internal/testdata/resource.go b/internal/otel_collector/internal/testdata/resource.go new file mode 100644 index 00000000000..f4870274180 --- /dev/null +++ b/internal/otel_collector/internal/testdata/resource.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func initResource1(r pdata.Resource) { + initResourceAttributes1(r.Attributes()) +} + +func generateOtlpResource1() otlpresource.Resource { + return otlpresource.Resource{ + Attributes: generateOtlpResourceAttributes1(), + } +} + +func initResource2(r pdata.Resource) { + initResourceAttributes2(r.Attributes()) +} + +func generateOtlpResource2() otlpresource.Resource { + return otlpresource.Resource{ + Attributes: generateOtlpResourceAttributes2(), + } +} diff --git a/internal/otel_collector/internal/testdata/trace.go b/internal/otel_collector/internal/testdata/trace.go new file mode 100644 index 00000000000..94a73f637e6 --- /dev/null +++ b/internal/otel_collector/internal/testdata/trace.go @@ -0,0 +1,313 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + "time" + + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +var ( + TestSpanStartTime = time.Date(2020, 2, 11, 20, 26, 12, 321, time.UTC) + TestSpanStartTimestamp = pdata.TimestampUnixNano(TestSpanStartTime.UnixNano()) + + TestSpanEventTime = time.Date(2020, 2, 11, 20, 26, 13, 123, time.UTC) + TestSpanEventTimestamp = pdata.TimestampUnixNano(TestSpanEventTime.UnixNano()) + + TestSpanEndTime = time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC) + TestSpanEndTimestamp = pdata.TimestampUnixNano(TestSpanEndTime.UnixNano()) +) + +func GenerateTraceDataEmpty() pdata.Traces { + td := pdata.NewTraces() + return td +} + +func generateTraceOtlpEmpty() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans(nil) +} + +func GenerateTraceDataOneEmptyResourceSpans() pdata.Traces { + td := GenerateTraceDataEmpty() + td.ResourceSpans().Resize(1) + return td +} + +func generateTraceOtlpOneEmptyResourceSpans() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + {}, + } +} + +func GenerateTraceDataNoLibraries() pdata.Traces { + td := GenerateTraceDataOneEmptyResourceSpans() + rs0 := td.ResourceSpans().At(0) + initResource1(rs0.Resource()) + return td +} + +func generateTraceOtlpNoLibraries() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + { + Resource: generateOtlpResource1(), + }, + } +} + +func GenerateTraceDataOneEmptyInstrumentationLibrary() pdata.Traces { + td := GenerateTraceDataNoLibraries() + rs0 := td.ResourceSpans().At(0) + rs0.InstrumentationLibrarySpans().Resize(1) + return td +} + +func generateTraceOtlpOneEmptyInstrumentationLibrary() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + { + Resource: generateOtlpResource1(), + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + {}, + }, + }, + } +} + +func GenerateTraceDataOneSpanNoResource() pdata.Traces { + td := GenerateTraceDataOneEmptyResourceSpans() + rs0 := td.ResourceSpans().At(0) + rs0.InstrumentationLibrarySpans().Resize(1) + rs0ils0 := rs0.InstrumentationLibrarySpans().At(0) + rs0ils0.Spans().Resize(1) + fillSpanOne(rs0ils0.Spans().At(0)) + return td +} + +func generateTraceOtlpOneSpanNoResource() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + generateOtlpSpanOne(), + }, + }, + }, + }, + } +} + +func GenerateTraceDataOneSpan() pdata.Traces { + td := GenerateTraceDataOneEmptyInstrumentationLibrary() + rs0ils0 := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0) + rs0ils0.Spans().Resize(1) + fillSpanOne(rs0ils0.Spans().At(0)) + return td +} + +func generateTraceOtlpOneSpan() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + { + Resource: generateOtlpResource1(), + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + generateOtlpSpanOne(), + }, + }, + }, + }, + } +} + +func GenerateTraceDataTwoSpansSameResource() pdata.Traces { + td := GenerateTraceDataOneEmptyInstrumentationLibrary() + rs0ils0 := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0) + rs0ils0.Spans().Resize(2) + fillSpanOne(rs0ils0.Spans().At(0)) + fillSpanTwo(rs0ils0.Spans().At(1)) + return td +} + +// GenerateTraceOtlpSameResourceTwoSpans returns the OTLP representation of the GenerateTraceOtlpSameResourceTwoSpans. +func GenerateTraceOtlpSameResourceTwoSpans() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + { + Resource: generateOtlpResource1(), + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + generateOtlpSpanOne(), + generateOtlpSpanTwo(), + }, + }, + }, + }, + } +} + +func GenerateTraceDataTwoSpansSameResourceOneDifferent() pdata.Traces { + td := pdata.NewTraces() + td.ResourceSpans().Resize(2) + rs0 := td.ResourceSpans().At(0) + initResource1(rs0.Resource()) + rs0.InstrumentationLibrarySpans().Resize(1) + rs0ils0 := rs0.InstrumentationLibrarySpans().At(0) + rs0ils0.Spans().Resize(2) + fillSpanOne(rs0ils0.Spans().At(0)) + fillSpanTwo(rs0ils0.Spans().At(1)) + rs1 := td.ResourceSpans().At(1) + initResource2(rs1.Resource()) + rs1.InstrumentationLibrarySpans().Resize(1) + rs1ils0 := rs1.InstrumentationLibrarySpans().At(0) + rs1ils0.Spans().Resize(1) + fillSpanThree(rs1ils0.Spans().At(0)) + return td +} + +func GenerateTraceDataManySpansSameResource(spansCount int) pdata.Traces { + td := GenerateTraceDataOneEmptyInstrumentationLibrary() + rs0ils0 := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0) + rs0ils0.Spans().Resize(spansCount) + for i := 0; i < spansCount; i++ { + fillSpanOne(rs0ils0.Spans().At(i)) + } + return td +} + +func generateTraceOtlpTwoSpansSameResourceOneDifferent() []*otlptrace.ResourceSpans { + return []*otlptrace.ResourceSpans{ + { + Resource: generateOtlpResource1(), + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + generateOtlpSpanOne(), + generateOtlpSpanTwo(), + }, + }, + }, + }, + { + Resource: generateOtlpResource2(), + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + generateOtlpSpanThree(), + }, + }, + }, + }, + } +} + +func fillSpanOne(span pdata.Span) { + span.SetName("operationA") + span.SetStartTime(TestSpanStartTimestamp) + span.SetEndTime(TestSpanEndTimestamp) + span.SetDroppedAttributesCount(1) + evs := span.Events() + evs.Resize(2) + ev0 := evs.At(0) + ev0.SetTimestamp(TestSpanEventTimestamp) + ev0.SetName("event-with-attr") + initSpanEventAttributes(ev0.Attributes()) + ev0.SetDroppedAttributesCount(2) + ev1 := evs.At(1) + ev1.SetTimestamp(TestSpanEventTimestamp) + ev1.SetName("event") + ev1.SetDroppedAttributesCount(2) + span.SetDroppedEventsCount(1) + status := span.Status() + status.SetCode(pdata.StatusCodeError) + status.SetMessage("status-cancelled") +} + +func generateOtlpSpanOne() *otlptrace.Span { + return &otlptrace.Span{ + Name: "operationA", + StartTimeUnixNano: uint64(TestSpanStartTimestamp), + EndTimeUnixNano: uint64(TestSpanEndTimestamp), + DroppedAttributesCount: 1, + Events: []*otlptrace.Span_Event{ + { + Name: "event-with-attr", + TimeUnixNano: uint64(TestSpanEventTimestamp), + Attributes: generateOtlpSpanEventAttributes(), + DroppedAttributesCount: 2, + }, + { + Name: "event", + TimeUnixNano: uint64(TestSpanEventTimestamp), + DroppedAttributesCount: 2, + }, + }, + DroppedEventsCount: 1, + Status: otlptrace.Status{ + Code: otlptrace.Status_STATUS_CODE_ERROR, + DeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_UNKNOWN_ERROR, + Message: "status-cancelled", + }, + } +} + +func fillSpanTwo(span pdata.Span) { + span.SetName("operationB") + span.SetStartTime(TestSpanStartTimestamp) + span.SetEndTime(TestSpanEndTimestamp) + span.Links().Resize(2) + initSpanLinkAttributes(span.Links().At(0).Attributes()) + span.Links().At(0).SetDroppedAttributesCount(4) + span.Links().At(1).SetDroppedAttributesCount(4) + span.SetDroppedLinksCount(3) +} + +func generateOtlpSpanTwo() *otlptrace.Span { + return &otlptrace.Span{ + Name: "operationB", + StartTimeUnixNano: uint64(TestSpanStartTimestamp), + EndTimeUnixNano: uint64(TestSpanEndTimestamp), + Links: []*otlptrace.Span_Link{ + { + Attributes: generateOtlpSpanLinkAttributes(), + DroppedAttributesCount: 4, + }, + { + DroppedAttributesCount: 4, + }, + }, + DroppedLinksCount: 3, + } +} + +func fillSpanThree(span pdata.Span) { + span.SetName("operationC") + span.SetStartTime(TestSpanStartTimestamp) + span.SetEndTime(TestSpanEndTimestamp) + initSpanAttributes(span.Attributes()) + span.SetDroppedAttributesCount(5) +} + +func generateOtlpSpanThree() *otlptrace.Span { + return &otlptrace.Span{ + Name: "operationC", + StartTimeUnixNano: uint64(TestSpanStartTimestamp), + EndTimeUnixNano: uint64(TestSpanEndTimestamp), + Attributes: generateOtlpSpanAttributes(), + DroppedAttributesCount: 5, + } +} diff --git a/internal/otel_collector/internal/testdata/trace_test.go b/internal/otel_collector/internal/testdata/trace_test.go new file mode 100644 index 00000000000..aebddf6d0dd --- /dev/null +++ b/internal/otel_collector/internal/testdata/trace_test.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +type traceTestCase struct { + name string + td pdata.Traces + otlp []*otlptrace.ResourceSpans +} + +func generateAllTraceTestCases() []traceTestCase { + return []traceTestCase{ + { + name: "empty", + td: GenerateTraceDataEmpty(), + otlp: generateTraceOtlpEmpty(), + }, + { + name: "one-empty-resource-spans", + td: GenerateTraceDataOneEmptyResourceSpans(), + otlp: generateTraceOtlpOneEmptyResourceSpans(), + }, + { + name: "no-libraries", + td: GenerateTraceDataNoLibraries(), + otlp: generateTraceOtlpNoLibraries(), + }, + { + name: "one-empty-instrumentation-library", + td: GenerateTraceDataOneEmptyInstrumentationLibrary(), + otlp: generateTraceOtlpOneEmptyInstrumentationLibrary(), + }, + { + name: "one-span-no-resource", + td: GenerateTraceDataOneSpanNoResource(), + otlp: generateTraceOtlpOneSpanNoResource(), + }, + { + name: "one-span", + td: GenerateTraceDataOneSpan(), + otlp: generateTraceOtlpOneSpan(), + }, + { + name: "two-spans-same-resource", + td: GenerateTraceDataTwoSpansSameResource(), + otlp: GenerateTraceOtlpSameResourceTwoSpans(), + }, + { + name: "two-spans-same-resource-one-different", + td: GenerateTraceDataTwoSpansSameResourceOneDifferent(), + otlp: generateTraceOtlpTwoSpansSameResourceOneDifferent(), + }, + } +} + +func TestToFromOtlpTrace(t *testing.T) { + allTestCases := generateAllTraceTestCases() + // Ensure NumTraceTests gets updated. + for i := range allTestCases { + test := allTestCases[i] + t.Run(test.name, func(t *testing.T) { + td := pdata.TracesFromOtlp(test.otlp) + assert.EqualValues(t, test.td, td) + otlp := pdata.TracesToOtlp(td) + assert.EqualValues(t, test.otlp, otlp) + }) + } +} diff --git a/internal/otel_collector/internal/version/version.go b/internal/otel_collector/internal/version/version.go new file mode 100644 index 00000000000..9e797c896c0 --- /dev/null +++ b/internal/otel_collector/internal/version/version.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "bytes" + "fmt" + "runtime" +) + +const ( + buildDev = "dev" + buildRelease = "release" +) + +// Version variable will be replaced at link time after `make` has been run. +var Version = "latest" + +// GitHash variable will be replaced at link time after `make` has been run. +var GitHash = "" + +// BuildType should be one of (dev, release). +var BuildType = buildDev + +// IsDevBuild returns true if this is a development (local) build. +func IsDevBuild() bool { + return BuildType == buildDev +} + +// IsReleaseBuild returns true if this is a release build. +func IsReleaseBuild() bool { + return BuildType == buildRelease +} + +// InfoVar is a singleton instance of the Info struct. +var InfoVar = Info([][2]string{ + {"Version", Version}, + {"GitHash", GitHash}, + {"BuildType", BuildType}, + {"Goversion", runtime.Version()}, + {"OS", runtime.GOOS}, + {"Architecture", runtime.GOARCH}, + // Add other valuable build-time information here. +}) + +// Info has properties about the build and runtime. +type Info [][2]string + +// String returns a formatted string, with linebreaks, intended to be displayed +// on stdout. +func (i Info) String() string { + buf := new(bytes.Buffer) + maxRow1Alignment := 0 + for _, prop := range i { + if cl0 := len(prop[0]); cl0 > maxRow1Alignment { + maxRow1Alignment = cl0 + } + } + + for _, prop := range i { + // Then finally print them with left alignment + fmt.Fprintf(buf, "%*s %s\n", -maxRow1Alignment, prop[0], prop[1]) + } + return buf.String() +} diff --git a/internal/otel_collector/internal/version/version_test.go b/internal/otel_collector/internal/version/version_test.go new file mode 100644 index 00000000000..0cf8886ec9b --- /dev/null +++ b/internal/otel_collector/internal/version/version_test.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInfoString(t *testing.T) { + infoString := InfoVar.String() + for _, el := range InfoVar { + assert.True(t, strings.Contains(infoString, el[0])) + assert.True(t, strings.Contains(infoString, el[1])) + } +} diff --git a/internal/otel_collector/obsreport/doc.go b/internal/otel_collector/obsreport/doc.go new file mode 100644 index 00000000000..e8124eedcdf --- /dev/null +++ b/internal/otel_collector/obsreport/doc.go @@ -0,0 +1,112 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package obsreport provides unified and consistent observability signals ( +// metrics, tracing, etc) for components of the OpenTelemetry collector. +// +// The function Configure is used to control which signals are going to be +// generated. It provides functions for the typical operations of receivers, +// processors, and exporters. +// +// Receivers should use the respective start and end according to the data type +// being received, ie.: +// +// * TraceData receive operations should use the pair: +// StartTraceDataReceiveOp/EndTraceDataReceiveOp +// +// * Metrics receive operations should use the pair: +// StartMetricsReceiveOp/EndMetricsReceiveOp +// +// Similar for exporters: +// +// * TraceData export operations should use the pair: +// StartTraceDataExportOp/EndTraceDataExportOp +// +// * Metrics export operations should use the pair: +// StartMetricsExportOp/EndMetricsExportOp +// +// The package is capable of generating legacy metrics by using the +// observability package allowing a controlled transition from legacy to the +// new metrics. The goal is to eventually remove the legacy metrics and use only +// the new metrics. +// +// The main differences regarding the legacy metrics are: +// +// 1. "Amount of metric data" is measured as metric points (ie.: a single value +// in time), contrast it with number of time series used legacy. Number of +// metric data points is a more general concept regarding various metric +// formats. +// +// 2. Exporters measure the number of items, ie.: number of spans or metric +// points, that were sent and the ones for which the attempt to send failed. +// For more information about this see Notes below about reporting data loss. +// +// 3. All measurements of "amount of data" used in the new metrics for receivers +// and exporters should reflect their native formats, not the internal format +// used in the Collector. This is to facilitate reconciliation between Collector, +// client and backend. For instance: certain metric formats do not provide +// direct support for histograms and have predefined conventions to represent +// those, this conversion may end with a different number of time series and +// data points than the internal Collector format. +// +// Notes: +// +// * Data loss should be recorded only when the component itself remove the data +// from the pipeline. Legacy metrics for receivers used "dropped" in their names +// but these could be non-zero under normal operations and reflected no actual +// data loss when components like the "queued_retry" are used. New metrics +// were renamed to avoid this misunderstanding. Here are the general +// recommendations to report data loss: +// +// * Receivers reporting errors to clients typically result in the client +// re-sending the same data so it is more correct to report "receive errors", +// not actual data loss. +// +// * Exporters need to report individual failures to send data, but on +// typical production pipelines processors usually take care of retries, +// so these should be reported as "send errors". +// +// * Data "filtered out" should have its own metrics and not be confused +// with dropped data. +// +// Naming Convention for New Metrics +// +// Common Metrics: +// Metrics shared by different components should follow the convention below: +// +// `/` +// +// As a label the metric should have at least `{=""}` where +// `` is the name used in the configuration for the instance of the +// component, eg.: +// +// `receiver/accepted_spans{receiver="opencensus",...}` +// `exporter/sent_spans{exporter="jaeger/prod",...}` +// +// Component Specific Metrics: +// These metrics are implemented by specific components, eg.: batch processor. +// The general pattern is the same as the common metrics but with the addition +// of the component type (as it appears in the configuration) before the actual +// metric: +// +// `//` +// +// Even metrics exclusive to a single type should follow the conventions above +// and also include the type (as written in the configuration) as part of the +// metric name since there could be multiple instances of the same type in +// different pipelines, eg.: +// +// `processor/batch/batch_size_trigger_send{processor="batch/dev",...}` +// +package obsreport diff --git a/internal/otel_collector/obsreport/observability.go b/internal/otel_collector/obsreport/observability.go new file mode 100644 index 00000000000..8f04bce2c5d --- /dev/null +++ b/internal/otel_collector/obsreport/observability.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreport + +// This file contains helpers that are useful to add observability +// with metrics and tracing using OpenCensus to the various pieces +// of the service. + +import ( + "go.opencensus.io/plugin/ocgrpc" + "google.golang.org/grpc" +) + +// GRPCServerWithObservabilityEnabled creates a gRPC server that at a bare minimum has +// the OpenCensus ocgrpc server stats handler enabled for tracing and stats. +// Use it instead of invoking grpc.NewServer directly. +func GRPCServerWithObservabilityEnabled(extraOpts ...grpc.ServerOption) *grpc.Server { + opts := append(extraOpts, grpc.StatsHandler(&ocgrpc.ServerHandler{})) + return grpc.NewServer(opts...) +} diff --git a/internal/otel_collector/obsreport/obsreport.go b/internal/otel_collector/obsreport/obsreport.go new file mode 100644 index 00000000000..552c11e34a5 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreport.go @@ -0,0 +1,163 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreport + +import ( + "context" + "strings" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/config/configtelemetry" +) + +const ( + nameSep = "/" +) + +var ( + gLevel = configtelemetry.LevelBasic + + okStatus = trace.Status{Code: trace.StatusCodeOK} +) + +// setParentLink tries to retrieve a span from parentCtx and if one exists +// sets its SpanID, TraceID as a link to the given child Span. +// It returns true only if it retrieved a parent span from the context. +// +// This is typically used when the parentCtx may already have a trace and is +// long lived (eg.: an gRPC stream, or TCP connection) and one desires distinct +// traces for individual operations under the long lived trace associated to +// the parentCtx. This function is a helper that encapsulates the work of +// linking the short lived trace/span to the longer one. +func setParentLink(parentCtx context.Context, childSpan *trace.Span) bool { + parentSpanFromRPC := trace.FromContext(parentCtx) + if parentSpanFromRPC == nil { + return false + } + + psc := parentSpanFromRPC.SpanContext() + childSpan.AddLink(trace.Link{ + SpanID: psc.SpanID, + TraceID: psc.TraceID, + Type: trace.LinkTypeParent, + }) + return true +} + +// Configure is used to control the settings that will be used by the obsreport +// package. +func Configure(level configtelemetry.Level) (views []*view.View) { + gLevel = level + + if gLevel != configtelemetry.LevelNone { + gProcessorObsReport.level = level + views = append(views, AllViews()...) + } + + return views +} + +func buildComponentPrefix(componentPrefix, configType string) string { + if !strings.HasSuffix(componentPrefix, nameSep) { + componentPrefix += nameSep + } + if configType == "" { + return componentPrefix + } + return componentPrefix + configType + nameSep +} + +// AllViews return the list of all views that needs to be configured. +func AllViews() (views []*view.View) { + // Receiver views. + measures := []*stats.Int64Measure{ + mReceiverAcceptedSpans, + mReceiverRefusedSpans, + mReceiverAcceptedMetricPoints, + mReceiverRefusedMetricPoints, + mReceiverAcceptedLogRecords, + mReceiverRefusedLogRecords, + } + tagKeys := []tag.Key{ + tagKeyReceiver, tagKeyTransport, + } + views = append(views, genViews(measures, tagKeys, view.Sum())...) + + // Scraper views. + measures = []*stats.Int64Measure{ + mScraperScrapedMetricPoints, + mScraperErroredMetricPoints, + } + tagKeys = []tag.Key{tagKeyReceiver, tagKeyScraper} + views = append(views, genViews(measures, tagKeys, view.Sum())...) + + // Exporter views. + measures = []*stats.Int64Measure{ + mExporterSentSpans, + mExporterFailedToSendSpans, + mExporterSentMetricPoints, + mExporterFailedToSendMetricPoints, + mExporterSentLogRecords, + mExporterFailedToSendLogRecords, + } + tagKeys = []tag.Key{tagKeyExporter} + views = append(views, genViews(measures, tagKeys, view.Sum())...) + + // Processor views. + measures = []*stats.Int64Measure{ + mProcessorAcceptedSpans, + mProcessorRefusedSpans, + mProcessorDroppedSpans, + mProcessorAcceptedMetricPoints, + mProcessorRefusedMetricPoints, + mProcessorDroppedMetricPoints, + mProcessorAcceptedLogRecords, + mProcessorRefusedLogRecords, + mProcessorDroppedLogRecords, + } + tagKeys = []tag.Key{tagKeyProcessor} + views = append(views, genViews(measures, tagKeys, view.Sum())...) + + return views +} + +func genViews( + measures []*stats.Int64Measure, + tagKeys []tag.Key, + aggregation *view.Aggregation, +) []*view.View { + views := make([]*view.View, 0, len(measures)) + for _, measure := range measures { + views = append(views, &view.View{ + Name: measure.Name(), + Description: measure.Description(), + TagKeys: tagKeys, + Measure: measure, + Aggregation: aggregation, + }) + } + return views +} + +func errToStatus(err error) trace.Status { + if err != nil { + return trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()} + } + return okStatus +} diff --git a/internal/otel_collector/obsreport/obsreport_exporter.go b/internal/otel_collector/obsreport/obsreport_exporter.go new file mode 100644 index 00000000000..5059b5114b3 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreport_exporter.go @@ -0,0 +1,190 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreport + +import ( + "context" + + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/config/configtelemetry" +) + +const ( + // Key used to identify exporters in metrics and traces. + ExporterKey = "exporter" + + // Key used to track spans sent by exporters. + SentSpansKey = "sent_spans" + // Key used to track spans that failed to be sent by exporters. + FailedToSendSpansKey = "send_failed_spans" + + // Key used to track metric points sent by exporters. + SentMetricPointsKey = "sent_metric_points" + // Key used to track metric points that failed to be sent by exporters. + FailedToSendMetricPointsKey = "send_failed_metric_points" + + // Key used to track logs sent by exporters. + SentLogRecordsKey = "sent_log_records" + // Key used to track logs that failed to be sent by exporters. + FailedToSendLogRecordsKey = "send_failed_log_records" +) + +var ( + tagKeyExporter, _ = tag.NewKey(ExporterKey) + + exporterPrefix = ExporterKey + nameSep + exportTraceDataOperationSuffix = nameSep + "traces" + exportMetricsOperationSuffix = nameSep + "metrics" + exportLogsOperationSuffix = nameSep + "logs" + + // Exporter metrics. Any count of data items below is in the final format + // that they were sent, reasoning: reconciliation is easier if measurements + // on backend and exporter are expected to be the same. Translation issues + // that result in a different number of elements should be reported in a + // separate way. + mExporterSentSpans = stats.Int64( + exporterPrefix+SentSpansKey, + "Number of spans successfully sent to destination.", + stats.UnitDimensionless) + mExporterFailedToSendSpans = stats.Int64( + exporterPrefix+FailedToSendSpansKey, + "Number of spans in failed attempts to send to destination.", + stats.UnitDimensionless) + mExporterSentMetricPoints = stats.Int64( + exporterPrefix+SentMetricPointsKey, + "Number of metric points successfully sent to destination.", + stats.UnitDimensionless) + mExporterFailedToSendMetricPoints = stats.Int64( + exporterPrefix+FailedToSendMetricPointsKey, + "Number of metric points in failed attempts to send to destination.", + stats.UnitDimensionless) + mExporterSentLogRecords = stats.Int64( + exporterPrefix+SentLogRecordsKey, + "Number of log record successfully sent to destination.", + stats.UnitDimensionless) + mExporterFailedToSendLogRecords = stats.Int64( + exporterPrefix+FailedToSendLogRecordsKey, + "Number of log records in failed attempts to send to destination.", + stats.UnitDimensionless) +) + +// ExporterContext adds the keys used when recording observability metrics to +// the given context returning the newly created context. This context should +// be used in related calls to the obsreport functions so metrics are properly +// recorded. +func ExporterContext(ctx context.Context, exporterName string) context.Context { + ctx, _ = tag.New(ctx, tag.Upsert(tagKeyExporter, exporterName, tag.WithTTL(tag.TTLNoPropagation))) + return ctx +} + +type ExporterObsReport struct { + level configtelemetry.Level + exporterName string + mutators []tag.Mutator +} + +func NewExporterObsReport(level configtelemetry.Level, exporterName string) *ExporterObsReport { + return &ExporterObsReport{ + level: level, + exporterName: exporterName, + mutators: []tag.Mutator{tag.Upsert(tagKeyProcessor, exporterName, tag.WithTTL(tag.TTLNoPropagation))}, + } +} + +// StartTracesExportOp is called at the start of an Export operation. +// The returned context should be used in other calls to the ExporterObsReport functions +// dealing with the same export operation. +func (eor *ExporterObsReport) StartTracesExportOp(ctx context.Context) context.Context { + return eor.startSpan(ctx, exportTraceDataOperationSuffix) +} + +// EndTracesExportOp completes the export operation that was started with StartTracesExportOp. +func (eor *ExporterObsReport) EndTracesExportOp(ctx context.Context, numSpans int, err error) { + numSent, numFailedToSend := toNumItems(numSpans, err) + recordMetrics(ctx, numSent, numFailedToSend, mExporterSentSpans, mExporterFailedToSendSpans) + endSpan(ctx, err, numSent, numFailedToSend, SentSpansKey, FailedToSendSpansKey) +} + +// StartMetricsExportOp is called at the start of an Export operation. +// The returned context should be used in other calls to the ExporterObsReport functions +// dealing with the same export operation. +func (eor *ExporterObsReport) StartMetricsExportOp(ctx context.Context) context.Context { + return eor.startSpan(ctx, exportMetricsOperationSuffix) +} + +// EndMetricsExportOp completes the export operation that was started with +// StartMetricsExportOp. +func (eor *ExporterObsReport) EndMetricsExportOp(ctx context.Context, numMetricPoints int, err error) { + numSent, numFailedToSend := toNumItems(numMetricPoints, err) + recordMetrics(ctx, numSent, numFailedToSend, mExporterSentMetricPoints, mExporterFailedToSendMetricPoints) + endSpan(ctx, err, numSent, numFailedToSend, SentMetricPointsKey, FailedToSendMetricPointsKey) +} + +// StartLogsExportOp is called at the start of an Export operation. +// The returned context should be used in other calls to the ExporterObsReport functions +// dealing with the same export operation. +func (eor *ExporterObsReport) StartLogsExportOp(ctx context.Context) context.Context { + return eor.startSpan(ctx, exportLogsOperationSuffix) +} + +// EndLogsExportOp completes the export operation that was started with StartLogsExportOp. +func (eor *ExporterObsReport) EndLogsExportOp(ctx context.Context, numLogRecords int, err error) { + numSent, numFailedToSend := toNumItems(numLogRecords, err) + recordMetrics(ctx, numSent, numFailedToSend, mExporterSentLogRecords, mExporterFailedToSendLogRecords) + endSpan(ctx, err, numSent, numFailedToSend, SentLogRecordsKey, FailedToSendLogRecordsKey) +} + +// startSpan creates the span used to trace the operation. Returning +// the updated context and the created span. +func (eor *ExporterObsReport) startSpan(ctx context.Context, operationSuffix string) context.Context { + spanName := exporterPrefix + eor.exporterName + operationSuffix + ctx, _ = trace.StartSpan(ctx, spanName) + return ctx +} + +func recordMetrics(ctx context.Context, numSent, numFailedToSend int64, sentMeasure, failedToSendMeasure *stats.Int64Measure) { + if gLevel == configtelemetry.LevelNone { + return + } + stats.Record( + ctx, + sentMeasure.M(numSent), + failedToSendMeasure.M(numFailedToSend)) +} + +func endSpan(ctx context.Context, err error, numSent, numFailedToSend int64, sentItemsKey, failedToSendItemsKey string) { + span := trace.FromContext(ctx) + // End span according to errors. + if span.IsRecordingEvents() { + span.AddAttributes( + trace.Int64Attribute( + sentItemsKey, numSent), + trace.Int64Attribute( + failedToSendItemsKey, numFailedToSend), + ) + span.SetStatus(errToStatus(err)) + } + span.End() +} + +func toNumItems(numExportedItems int, err error) (int64, int64) { + if err != nil { + return 0, int64(numExportedItems) + } + return int64(numExportedItems), 0 +} diff --git a/internal/otel_collector/obsreport/obsreport_processor.go b/internal/otel_collector/obsreport/obsreport_processor.go new file mode 100644 index 00000000000..2725e83d72d --- /dev/null +++ b/internal/otel_collector/obsreport/obsreport_processor.go @@ -0,0 +1,246 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreport + +import ( + "context" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + + "go.opentelemetry.io/collector/config/configtelemetry" +) + +const ( + // Key used to identify processors in metrics and traces. + ProcessorKey = "processor" + + // Key used to identify spans dropped by the Collector. + DroppedSpansKey = "dropped_spans" + + // Key used to identify metric points dropped by the Collector. + DroppedMetricPointsKey = "dropped_metric_points" + + // Key used to identify log records dropped by the Collector. + DroppedLogRecordsKey = "dropped_log_records" +) + +var ( + tagKeyProcessor, _ = tag.NewKey(ProcessorKey) + + processorPrefix = ProcessorKey + nameSep + + // Processor metrics. Any count of data items below is in the internal format + // of the collector since processors only deal with internal format. + mProcessorAcceptedSpans = stats.Int64( + processorPrefix+AcceptedSpansKey, + "Number of spans successfully pushed into the next component in the pipeline.", + stats.UnitDimensionless) + mProcessorRefusedSpans = stats.Int64( + processorPrefix+RefusedSpansKey, + "Number of spans that were rejected by the next component in the pipeline.", + stats.UnitDimensionless) + mProcessorDroppedSpans = stats.Int64( + processorPrefix+DroppedSpansKey, + "Number of spans that were dropped.", + stats.UnitDimensionless) + mProcessorAcceptedMetricPoints = stats.Int64( + processorPrefix+AcceptedMetricPointsKey, + "Number of metric points successfully pushed into the next component in the pipeline.", + stats.UnitDimensionless) + mProcessorRefusedMetricPoints = stats.Int64( + processorPrefix+RefusedMetricPointsKey, + "Number of metric points that were rejected by the next component in the pipeline.", + stats.UnitDimensionless) + mProcessorDroppedMetricPoints = stats.Int64( + processorPrefix+DroppedMetricPointsKey, + "Number of metric points that were dropped.", + stats.UnitDimensionless) + mProcessorAcceptedLogRecords = stats.Int64( + processorPrefix+AcceptedLogRecordsKey, + "Number of log records successfully pushed into the next component in the pipeline.", + stats.UnitDimensionless) + mProcessorRefusedLogRecords = stats.Int64( + processorPrefix+RefusedLogRecordsKey, + "Number of log records that were rejected by the next component in the pipeline.", + stats.UnitDimensionless) + mProcessorDroppedLogRecords = stats.Int64( + processorPrefix+DroppedLogRecordsKey, + "Number of log records that were dropped.", + stats.UnitDimensionless) +) + +// BuildProcessorCustomMetricName is used to be build a metric name following +// the standards used in the Collector. The configType should be the same +// value used to identify the type on the config. +func BuildProcessorCustomMetricName(configType, metric string) string { + return buildComponentPrefix(processorPrefix, configType) + metric +} + +// ProcessorMetricViews builds the metric views for custom metrics of processors. +func ProcessorMetricViews(configType string, legacyViews []*view.View) []*view.View { + var allViews []*view.View + if gLevel != configtelemetry.LevelNone { + for _, legacyView := range legacyViews { + // Ignore any nil entry and views without measure or aggregation. + // These can't be registered but some code registering legacy views may + // ignore the errors. + if legacyView == nil || legacyView.Measure == nil || legacyView.Aggregation == nil { + continue + } + newView := *legacyView + viewName := legacyView.Name + if viewName == "" { + viewName = legacyView.Measure.Name() + } + newView.Name = BuildProcessorCustomMetricName(configType, viewName) + allViews = append(allViews, &newView) + } + } + + return allViews +} + +var gProcessorObsReport = &ProcessorObsReport{level: configtelemetry.LevelNone} + +type ProcessorObsReport struct { + level configtelemetry.Level + mutators []tag.Mutator +} + +func NewProcessorObsReport(level configtelemetry.Level, processorName string) *ProcessorObsReport { + return &ProcessorObsReport{ + level: level, + mutators: []tag.Mutator{tag.Upsert(tagKeyProcessor, processorName, tag.WithTTL(tag.TTLNoPropagation))}, + } +} + +// TracesAccepted reports that the trace data was accepted. +func (por *ProcessorObsReport) TracesAccepted(ctx context.Context, numSpans int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedSpans.M(int64(numSpans)), + mProcessorRefusedSpans.M(0), + mProcessorDroppedSpans.M(0), + ) + } +} + +// TracesRefused reports that the trace data was refused. +func (por *ProcessorObsReport) TracesRefused(ctx context.Context, numSpans int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedSpans.M(0), + mProcessorRefusedSpans.M(int64(numSpans)), + mProcessorDroppedSpans.M(0), + ) + } +} + +// TracesDropped reports that the trace data was dropped. +func (por *ProcessorObsReport) TracesDropped(ctx context.Context, numSpans int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedSpans.M(0), + mProcessorRefusedSpans.M(0), + mProcessorDroppedSpans.M(int64(numSpans)), + ) + } +} + +// MetricsAccepted reports that the metrics were accepted. +func (por *ProcessorObsReport) MetricsAccepted(ctx context.Context, numPoints int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedMetricPoints.M(int64(numPoints)), + mProcessorRefusedMetricPoints.M(0), + mProcessorDroppedMetricPoints.M(0), + ) + } +} + +// MetricsRefused reports that the metrics were refused. +func (por *ProcessorObsReport) MetricsRefused(ctx context.Context, numPoints int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedMetricPoints.M(0), + mProcessorRefusedMetricPoints.M(int64(numPoints)), + mProcessorDroppedMetricPoints.M(0), + ) + } +} + +// MetricsDropped reports that the metrics were dropped. +func (por *ProcessorObsReport) MetricsDropped(ctx context.Context, numPoints int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedMetricPoints.M(0), + mProcessorRefusedMetricPoints.M(0), + mProcessorDroppedMetricPoints.M(int64(numPoints)), + ) + } +} + +// LogsAccepted reports that the logs were accepted. +func (por *ProcessorObsReport) LogsAccepted(ctx context.Context, numRecords int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedLogRecords.M(int64(numRecords)), + mProcessorRefusedLogRecords.M(0), + mProcessorDroppedLogRecords.M(0), + ) + } +} + +// LogsRefused reports that the logs were refused. +func (por *ProcessorObsReport) LogsRefused(ctx context.Context, numRecords int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedLogRecords.M(0), + mProcessorRefusedLogRecords.M(int64(numRecords)), + mProcessorDroppedMetricPoints.M(0), + ) + } +} + +// LogsDropped reports that the logs were dropped. +func (por *ProcessorObsReport) LogsDropped(ctx context.Context, numRecords int) { + if por.level != configtelemetry.LevelNone { + stats.RecordWithTags( + ctx, + por.mutators, + mProcessorAcceptedLogRecords.M(0), + mProcessorRefusedLogRecords.M(0), + mProcessorDroppedLogRecords.M(int64(numRecords)), + ) + } +} diff --git a/internal/otel_collector/obsreport/obsreport_receiver.go b/internal/otel_collector/obsreport/obsreport_receiver.go new file mode 100644 index 00000000000..33148a03372 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreport_receiver.go @@ -0,0 +1,364 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreport + +import ( + "context" + + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" +) + +const ( + // Key used to identify receivers in metrics and traces. + ReceiverKey = "receiver" + // Key used to identify the transport used to received the data. + TransportKey = "transport" + // Key used to identify the format of the data received. + FormatKey = "format" + + // Key used to identify spans accepted by the Collector. + AcceptedSpansKey = "accepted_spans" + // Key used to identify spans refused (ie.: not ingested) by the Collector. + RefusedSpansKey = "refused_spans" + + // Key used to identify metric points accepted by the Collector. + AcceptedMetricPointsKey = "accepted_metric_points" + // Key used to identify metric points refused (ie.: not ingested) by the + // Collector. + RefusedMetricPointsKey = "refused_metric_points" + + // Key used to identify log records accepted by the Collector. + AcceptedLogRecordsKey = "accepted_log_records" + // Key used to identify log records refused (ie.: not ingested) by the + // Collector. + RefusedLogRecordsKey = "refused_log_records" +) + +var ( + tagKeyReceiver, _ = tag.NewKey(ReceiverKey) + tagKeyTransport, _ = tag.NewKey(TransportKey) + + receiverPrefix = ReceiverKey + nameSep + receiveTraceDataOperationSuffix = nameSep + "TraceDataReceived" + receiverMetricsOperationSuffix = nameSep + "MetricsReceived" + receiverLogsOperationSuffix = nameSep + "LogsReceived" + + // Receiver metrics. Any count of data items below is in the original format + // that they were received, reasoning: reconciliation is easier if measurements + // on clients and receiver are expected to be the same. Translation issues + // that result in a different number of elements should be reported in a + // separate way. + mReceiverAcceptedSpans = stats.Int64( + receiverPrefix+AcceptedSpansKey, + "Number of spans successfully pushed into the pipeline.", + stats.UnitDimensionless) + mReceiverRefusedSpans = stats.Int64( + receiverPrefix+RefusedSpansKey, + "Number of spans that could not be pushed into the pipeline.", + stats.UnitDimensionless) + mReceiverAcceptedMetricPoints = stats.Int64( + receiverPrefix+AcceptedMetricPointsKey, + "Number of metric points successfully pushed into the pipeline.", + stats.UnitDimensionless) + mReceiverRefusedMetricPoints = stats.Int64( + receiverPrefix+RefusedMetricPointsKey, + "Number of metric points that could not be pushed into the pipeline.", + stats.UnitDimensionless) + mReceiverAcceptedLogRecords = stats.Int64( + receiverPrefix+AcceptedLogRecordsKey, + "Number of log records successfully pushed into the pipeline.", + stats.UnitDimensionless) + mReceiverRefusedLogRecords = stats.Int64( + receiverPrefix+RefusedLogRecordsKey, + "Number of log records that could not be pushed into the pipeline.", + stats.UnitDimensionless) +) + +// StartReceiveOptions has the options related to starting a receive operation. +type StartReceiveOptions struct { + // LongLivedCtx when true indicates that the context passed in the call + // outlives the individual receive operation. See WithLongLivedCtx() for + // more information. + LongLivedCtx bool +} + +// StartReceiveOption function applues changes to StartReceiveOptions. +type StartReceiveOption func(*StartReceiveOptions) + +// WithLongLivedCtx indicates that the context passed in the call outlives the +// receive operation at hand. Typically the long lived context is associated +// to a connection, eg.: a gRPC stream or a TCP connection, for which many +// batches of data are received in individual operations without a corresponding +// new context per operation. +// +// Example: +// +// func (r *receiver) ClientConnect(ctx context.Context, rcvChan <-chan consumerdata.TraceData) { +// longLivedCtx := obsreport.ReceiverContext(ctx, r.config.Name(), r.transport, "") +// for { +// // Since the context outlives the individual receive operations call obsreport using +// // WithLongLivedCtx(). +// ctx := obsreport.StartTraceDataReceiveOp( +// longLivedCtx, +// r.config.Name(), +// r.transport, +// obsreport.WithLongLivedCtx()) +// +// td, ok := <-rcvChan +// var err error +// if ok { +// err = r.nextConsumer.ConsumeTraceData(ctx, td) +// } +// obsreport.EndTraceDataReceiveOp( +// ctx, +// r.format, +// len(td.Spans), +// err) +// if !ok { +// break +// } +// } +// } +// +func WithLongLivedCtx() StartReceiveOption { + return func(opts *StartReceiveOptions) { + opts.LongLivedCtx = true + } +} + +// StartTraceDataReceiveOp is called when a request is received from a client. +// The returned context should be used in other calls to the obsreport functions +// dealing with the same receive operation. +func StartTraceDataReceiveOp( + operationCtx context.Context, + receiver string, + transport string, + opt ...StartReceiveOption, +) context.Context { + return traceReceiveOp( + operationCtx, + receiver, + transport, + receiveTraceDataOperationSuffix, + opt...) +} + +// EndTraceDataReceiveOp completes the receive operation that was started with +// StartTraceDataReceiveOp. +func EndTraceDataReceiveOp( + receiverCtx context.Context, + format string, + numReceivedSpans int, + err error, +) { + endReceiveOp( + receiverCtx, + format, + numReceivedSpans, + err, + configmodels.TracesDataType, + ) +} + +// StartLogsReceiveOp is called when a request is received from a client. +// The returned context should be used in other calls to the obsreport functions +// dealing with the same receive operation. +func StartLogsReceiveOp( + operationCtx context.Context, + receiver string, + transport string, + opt ...StartReceiveOption, +) context.Context { + return traceReceiveOp( + operationCtx, + receiver, + transport, + receiverLogsOperationSuffix, + opt...) +} + +// EndLogsReceiveOp completes the receive operation that was started with +// StartLogsReceiveOp. +func EndLogsReceiveOp( + receiverCtx context.Context, + format string, + numReceivedLogRecords int, + err error, +) { + endReceiveOp( + receiverCtx, + format, + numReceivedLogRecords, + err, + configmodels.LogsDataType, + ) +} + +// StartMetricsReceiveOp is called when a request is received from a client. +// The returned context should be used in other calls to the obsreport functions +// dealing with the same receive operation. +func StartMetricsReceiveOp( + operationCtx context.Context, + receiver string, + transport string, + opt ...StartReceiveOption, +) context.Context { + return traceReceiveOp( + operationCtx, + receiver, + transport, + receiverMetricsOperationSuffix, + opt...) +} + +// EndMetricsReceiveOp completes the receive operation that was started with +// StartMetricsReceiveOp. +func EndMetricsReceiveOp( + receiverCtx context.Context, + format string, + numReceivedPoints int, + err error, +) { + endReceiveOp( + receiverCtx, + format, + numReceivedPoints, + err, + configmodels.MetricsDataType, + ) +} + +// ReceiverContext adds the keys used when recording observability metrics to +// the given context returning the newly created context. This context should +// be used in related calls to the obsreport functions so metrics are properly +// recorded. +func ReceiverContext( + ctx context.Context, + receiver string, + transport string, +) context.Context { + ctx, _ = tag.New(ctx, + tag.Upsert(tagKeyReceiver, receiver, tag.WithTTL(tag.TTLNoPropagation)), + tag.Upsert(tagKeyTransport, transport, tag.WithTTL(tag.TTLNoPropagation))) + + return ctx +} + +// traceReceiveOp creates the span used to trace the operation. Returning +// the updated context with the created span. +func traceReceiveOp( + receiverCtx context.Context, + receiverName string, + transport string, + operationSuffix string, + opt ...StartReceiveOption, +) context.Context { + var opts StartReceiveOptions + for _, o := range opt { + o(&opts) + } + + var ctx context.Context + var span *trace.Span + spanName := receiverPrefix + receiverName + operationSuffix + if !opts.LongLivedCtx { + ctx, span = trace.StartSpan(receiverCtx, spanName) + } else { + // Since the receiverCtx is long lived do not use it to start the span. + // This way this trace ends when the EndTraceDataReceiveOp is called. + // Here is safe to ignore the returned context since it is not used below. + _, span = trace.StartSpan(context.Background(), spanName) + + // If the long lived context has a parent span, then add it as a parent link. + setParentLink(receiverCtx, span) + + ctx = trace.NewContext(receiverCtx, span) + } + + if transport != "" { + span.AddAttributes(trace.StringAttribute(TransportKey, transport)) + } + return ctx +} + +// endReceiveOp records the observability signals at the end of an operation. +func endReceiveOp( + receiverCtx context.Context, + format string, + numReceivedItems int, + err error, + dataType configmodels.DataType, +) { + numAccepted := numReceivedItems + numRefused := 0 + if err != nil { + numAccepted = 0 + numRefused = numReceivedItems + } + + span := trace.FromContext(receiverCtx) + + if gLevel != configtelemetry.LevelNone { + var acceptedMeasure, refusedMeasure *stats.Int64Measure + switch dataType { + case configmodels.TracesDataType: + acceptedMeasure = mReceiverAcceptedSpans + refusedMeasure = mReceiverRefusedSpans + case configmodels.MetricsDataType: + acceptedMeasure = mReceiverAcceptedMetricPoints + refusedMeasure = mReceiverRefusedMetricPoints + case configmodels.LogsDataType: + acceptedMeasure = mReceiverAcceptedLogRecords + refusedMeasure = mReceiverRefusedLogRecords + } + + stats.Record( + receiverCtx, + acceptedMeasure.M(int64(numAccepted)), + refusedMeasure.M(int64(numRefused))) + } + + // end span according to errors + if span.IsRecordingEvents() { + var acceptedItemsKey, refusedItemsKey string + switch dataType { + case configmodels.TracesDataType: + acceptedItemsKey = AcceptedSpansKey + refusedItemsKey = RefusedSpansKey + case configmodels.MetricsDataType: + acceptedItemsKey = AcceptedMetricPointsKey + refusedItemsKey = RefusedMetricPointsKey + case configmodels.LogsDataType: + acceptedItemsKey = AcceptedLogRecordsKey + refusedItemsKey = RefusedLogRecordsKey + } + + span.AddAttributes( + trace.StringAttribute( + FormatKey, format), + trace.Int64Attribute( + acceptedItemsKey, int64(numAccepted)), + trace.Int64Attribute( + refusedItemsKey, int64(numRefused)), + ) + span.SetStatus(errToStatus(err)) + } + span.End() +} diff --git a/internal/otel_collector/obsreport/obsreport_scraper.go b/internal/otel_collector/obsreport/obsreport_scraper.go new file mode 100644 index 00000000000..ee02acb78a8 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreport_scraper.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreport + +import ( + "context" + + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/consumererror" +) + +const ( + // ScraperKey used to identify scrapers in metrics and traces. + ScraperKey = "scraper" + + // ScrapedMetricPointsKey used to identify metric points scraped by the + // Collector. + ScrapedMetricPointsKey = "scraped_metric_points" + // ErroredMetricPointsKey used to identify metric points errored (i.e. + // unable to be scraped) by the Collector. + ErroredMetricPointsKey = "errored_metric_points" +) + +const ( + scraperPrefix = ScraperKey + nameSep + scraperMetricsOperationSuffix = nameSep + "MetricsScraped" +) + +var ( + tagKeyScraper, _ = tag.NewKey(ScraperKey) + + mScraperScrapedMetricPoints = stats.Int64( + scraperPrefix+ScrapedMetricPointsKey, + "Number of metric points successfully scraped.", + stats.UnitDimensionless) + mScraperErroredMetricPoints = stats.Int64( + scraperPrefix+ErroredMetricPointsKey, + "Number of metric points that were unable to be scraped.", + stats.UnitDimensionless) +) + +// ScraperContext adds the keys used when recording observability metrics to +// the given context returning the newly created context. This context should +// be used in related calls to the obsreport functions so metrics are properly +// recorded. +func ScraperContext( + ctx context.Context, + receiver string, + scraper string, +) context.Context { + ctx, _ = tag.New(ctx, tag.Upsert(tagKeyReceiver, receiver, tag.WithTTL(tag.TTLNoPropagation))) + if scraper != "" { + ctx, _ = tag.New(ctx, tag.Upsert(tagKeyScraper, scraper, tag.WithTTL(tag.TTLNoPropagation))) + } + + return ctx +} + +// StartMetricsScrapeOp is called when a scrape operation is started. The +// returned context should be used in other calls to the obsreport functions +// dealing with the same scrape operation. +func StartMetricsScrapeOp( + scraperCtx context.Context, + receiver string, + scraper string, +) context.Context { + scraperName := receiver + if scraper != "" { + scraperName += "/" + scraper + } + + spanName := scraperPrefix + scraperName + scraperMetricsOperationSuffix + ctx, _ := trace.StartSpan(scraperCtx, spanName) + return ctx +} + +// EndMetricsScrapeOp completes the scrape operation that was started with +// StartMetricsScrapeOp. +func EndMetricsScrapeOp( + scraperCtx context.Context, + numScrapedMetrics int, + err error, +) { + numErroredMetrics := 0 + if err != nil { + if partialErr, isPartial := err.(consumererror.PartialScrapeError); isPartial { + numErroredMetrics = partialErr.Failed + } else { + numErroredMetrics = numScrapedMetrics + numScrapedMetrics = 0 + } + } + + span := trace.FromContext(scraperCtx) + + if gLevel != configtelemetry.LevelNone { + stats.Record( + scraperCtx, + mScraperScrapedMetricPoints.M(int64(numScrapedMetrics)), + mScraperErroredMetricPoints.M(int64(numErroredMetrics))) + } + + // end span according to errors + if span.IsRecordingEvents() { + span.AddAttributes( + trace.StringAttribute(FormatKey, string(configmodels.MetricsDataType)), + trace.Int64Attribute(ScrapedMetricPointsKey, int64(numScrapedMetrics)), + trace.Int64Attribute(ErroredMetricPointsKey, int64(numErroredMetrics)), + ) + + span.SetStatus(errToStatus(err)) + } + + span.End() +} diff --git a/internal/otel_collector/obsreport/obsreport_test.go b/internal/otel_collector/obsreport/obsreport_test.go new file mode 100644 index 00000000000..59733c72fc5 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreport_test.go @@ -0,0 +1,667 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// obsreport_test instead of just obsreport to avoid dependency cycle between +// obsreport_test and obsreporttest +package obsreport_test + +import ( + "context" + "errors" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +const ( + exporter = "fakeExporter" + processor = "fakeProcessor" + receiver = "fakeReicever" + scraper = "fakeScraper" + transport = "fakeTransport" + format = "fakeFormat" +) + +var ( + errFake = errors.New("errFake") + partialErrFake = consumererror.NewPartialScrapeError(errFake, 1) +) + +type receiveTestParams struct { + transport string + err error +} + +func TestConfigure(t *testing.T) { + tests := []struct { + name string + level configtelemetry.Level + wantViews []*view.View + }{ + { + name: "none", + level: configtelemetry.LevelNone, + }, + { + name: "basic", + level: configtelemetry.LevelBasic, + wantViews: obsreport.AllViews(), + }, + { + name: "normal", + level: configtelemetry.LevelNormal, + wantViews: obsreport.AllViews(), + }, + { + name: "detailed", + level: configtelemetry.LevelDetailed, + wantViews: obsreport.AllViews(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotViews := obsreport.Configure(tt.level) + assert.Equal(t, tt.wantViews, gotViews) + }) + } +} + +func TestReceiveTraceDataOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + receiverCtx := obsreport.ReceiverContext(parentCtx, receiver, transport) + params := []receiveTestParams{ + {transport, errFake}, + {"", nil}, + } + rcvdSpans := []int{13, 42} + for i, param := range params { + ctx := obsreport.StartTraceDataReceiveOp(receiverCtx, receiver, param.transport) + assert.NotNil(t, ctx) + + obsreport.EndTraceDataReceiveOp( + ctx, + format, + rcvdSpans[i], + param.err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(params), len(spans)) + + var acceptedSpans, refusedSpans int + for i, span := range spans { + assert.Equal(t, "receiver/"+receiver+"/TraceDataReceived", span.Name) + switch params[i].err { + case nil: + acceptedSpans += rcvdSpans[i] + assert.Equal(t, int64(rcvdSpans[i]), span.Attributes[obsreport.AcceptedSpansKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.RefusedSpansKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + refusedSpans += rcvdSpans[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.AcceptedSpansKey]) + assert.Equal(t, int64(rcvdSpans[i]), span.Attributes[obsreport.RefusedSpansKey]) + assert.Equal(t, params[i].err.Error(), span.Status.Message) + default: + t.Fatalf("unexpected param: %v", params[i]) + } + switch params[i].transport { + case "": + assert.NotContains(t, span.Attributes, obsreport.TransportKey) + default: + assert.Equal(t, params[i].transport, span.Attributes[obsreport.TransportKey]) + } + } + obsreporttest.CheckReceiverTracesViews(t, receiver, transport, int64(acceptedSpans), int64(refusedSpans)) +} + +func TestReceiveLogsOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + receiverCtx := obsreport.ReceiverContext(parentCtx, receiver, transport) + params := []receiveTestParams{ + {transport, errFake}, + {"", nil}, + } + rcvdLogRecords := []int{13, 42} + for i, param := range params { + ctx := obsreport.StartLogsReceiveOp(receiverCtx, receiver, param.transport) + assert.NotNil(t, ctx) + + obsreport.EndLogsReceiveOp( + ctx, + format, + rcvdLogRecords[i], + param.err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(params), len(spans)) + + var acceptedLogRecords, refusedLogRecords int + for i, span := range spans { + assert.Equal(t, "receiver/"+receiver+"/LogsReceived", span.Name) + switch params[i].err { + case nil: + acceptedLogRecords += rcvdLogRecords[i] + assert.Equal(t, int64(rcvdLogRecords[i]), span.Attributes[obsreport.AcceptedLogRecordsKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.RefusedLogRecordsKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + refusedLogRecords += rcvdLogRecords[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.AcceptedLogRecordsKey]) + assert.Equal(t, int64(rcvdLogRecords[i]), span.Attributes[obsreport.RefusedLogRecordsKey]) + assert.Equal(t, params[i].err.Error(), span.Status.Message) + default: + t.Fatalf("unexpected param: %v", params[i]) + } + switch params[i].transport { + case "": + assert.NotContains(t, span.Attributes, obsreport.TransportKey) + default: + assert.Equal(t, params[i].transport, span.Attributes[obsreport.TransportKey]) + } + } + obsreporttest.CheckReceiverLogsViews(t, receiver, transport, int64(acceptedLogRecords), int64(refusedLogRecords)) +} + +func TestReceiveMetricsOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + receiverCtx := obsreport.ReceiverContext(parentCtx, receiver, transport) + params := []receiveTestParams{ + {transport, errFake}, + {"", nil}, + } + rcvdMetricPts := []int{23, 29} + for i, param := range params { + ctx := obsreport.StartMetricsReceiveOp(receiverCtx, receiver, param.transport) + assert.NotNil(t, ctx) + + obsreport.EndMetricsReceiveOp( + ctx, + format, + rcvdMetricPts[i], + param.err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(params), len(spans)) + + var acceptedMetricPoints, refusedMetricPoints int + for i, span := range spans { + assert.Equal(t, "receiver/"+receiver+"/MetricsReceived", span.Name) + switch params[i].err { + case nil: + acceptedMetricPoints += rcvdMetricPts[i] + assert.Equal(t, int64(rcvdMetricPts[i]), span.Attributes[obsreport.AcceptedMetricPointsKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.RefusedMetricPointsKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + refusedMetricPoints += rcvdMetricPts[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.AcceptedMetricPointsKey]) + assert.Equal(t, int64(rcvdMetricPts[i]), span.Attributes[obsreport.RefusedMetricPointsKey]) + assert.Equal(t, params[i].err.Error(), span.Status.Message) + default: + t.Fatalf("unexpected param: %v", params[i]) + } + switch params[i].transport { + case "": + assert.NotContains(t, span.Attributes, obsreport.TransportKey) + default: + assert.Equal(t, params[i].transport, span.Attributes[obsreport.TransportKey]) + } + } + + obsreporttest.CheckReceiverMetricsViews(t, receiver, transport, int64(acceptedMetricPoints), int64(refusedMetricPoints)) +} + +func TestScrapeMetricsDataOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + receiverCtx := obsreport.ScraperContext(parentCtx, receiver, scraper) + errParams := []error{partialErrFake, errFake, nil} + scrapedMetricPts := []int{23, 29, 15} + for i, err := range errParams { + ctx := obsreport.StartMetricsScrapeOp(receiverCtx, receiver, scraper) + assert.NotNil(t, ctx) + + obsreport.EndMetricsScrapeOp( + ctx, + scrapedMetricPts[i], + err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(errParams), len(spans)) + + var scrapedMetricPoints, erroredMetricPoints int + for i, span := range spans { + assert.Equal(t, "scraper/"+receiver+"/"+scraper+"/MetricsScraped", span.Name) + switch errParams[i] { + case nil: + scrapedMetricPoints += scrapedMetricPts[i] + assert.Equal(t, int64(scrapedMetricPts[i]), span.Attributes[obsreport.ScrapedMetricPointsKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.ErroredMetricPointsKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + erroredMetricPoints += scrapedMetricPts[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.ScrapedMetricPointsKey]) + assert.Equal(t, int64(scrapedMetricPts[i]), span.Attributes[obsreport.ErroredMetricPointsKey]) + assert.Equal(t, errParams[i].Error(), span.Status.Message) + case partialErrFake: + scrapedMetricPoints += scrapedMetricPts[i] + erroredMetricPoints++ + assert.Equal(t, int64(scrapedMetricPts[i]), span.Attributes[obsreport.ScrapedMetricPointsKey]) + assert.Equal(t, int64(1), span.Attributes[obsreport.ErroredMetricPointsKey]) + assert.Equal(t, errParams[i].Error(), span.Status.Message) + default: + t.Fatalf("unexpected err param: %v", errParams[i]) + } + } + + obsreporttest.CheckScraperMetricsViews(t, receiver, scraper, int64(scrapedMetricPoints), int64(erroredMetricPoints)) +} + +func TestExportTraceDataOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + exporterCtx := obsreport.ExporterContext(parentCtx, exporter) + obsrep := obsreport.NewExporterObsReport(configtelemetry.LevelNormal, exporter) + errs := []error{nil, errFake} + numExportedSpans := []int{22, 14} + for i, err := range errs { + ctx := obsrep.StartTracesExportOp(exporterCtx) + assert.NotNil(t, ctx) + obsrep.EndTracesExportOp(ctx, numExportedSpans[i], err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(errs), len(spans)) + + var sentSpans, failedToSendSpans int + for i, span := range spans { + assert.Equal(t, "exporter/"+exporter+"/traces", span.Name) + switch errs[i] { + case nil: + sentSpans += numExportedSpans[i] + assert.Equal(t, int64(numExportedSpans[i]), span.Attributes[obsreport.SentSpansKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.FailedToSendSpansKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + failedToSendSpans += numExportedSpans[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.SentSpansKey]) + assert.Equal(t, int64(numExportedSpans[i]), span.Attributes[obsreport.FailedToSendSpansKey]) + assert.Equal(t, errs[i].Error(), span.Status.Message) + default: + t.Fatalf("unexpected error: %v", errs[i]) + } + } + + obsreporttest.CheckExporterTracesViews(t, exporter, int64(sentSpans), int64(failedToSendSpans)) +} + +func TestExportMetricsOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + exporterCtx := obsreport.ExporterContext(parentCtx, exporter) + obsrep := obsreport.NewExporterObsReport(configtelemetry.LevelNormal, exporter) + + errs := []error{nil, errFake} + toSendMetricPoints := []int{17, 23} + for i, err := range errs { + ctx := obsrep.StartMetricsExportOp(exporterCtx) + assert.NotNil(t, ctx) + + obsrep.EndMetricsExportOp(ctx, toSendMetricPoints[i], err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(errs), len(spans)) + + var sentMetricPoints, failedToSendMetricPoints int + for i, span := range spans { + assert.Equal(t, "exporter/"+exporter+"/metrics", span.Name) + switch errs[i] { + case nil: + sentMetricPoints += toSendMetricPoints[i] + assert.Equal(t, int64(toSendMetricPoints[i]), span.Attributes[obsreport.SentMetricPointsKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.FailedToSendMetricPointsKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + failedToSendMetricPoints += toSendMetricPoints[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.SentMetricPointsKey]) + assert.Equal(t, int64(toSendMetricPoints[i]), span.Attributes[obsreport.FailedToSendMetricPointsKey]) + assert.Equal(t, errs[i].Error(), span.Status.Message) + default: + t.Fatalf("unexpected error: %v", errs[i]) + } + } + + obsreporttest.CheckExporterMetricsViews(t, exporter, int64(sentMetricPoints), int64(failedToSendMetricPoints)) +} + +func TestExportLogsOp(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + parentCtx, parentSpan := trace.StartSpan(context.Background(), + t.Name(), trace.WithSampler(trace.AlwaysSample())) + defer parentSpan.End() + + exporterCtx := obsreport.ExporterContext(parentCtx, exporter) + obsrep := obsreport.NewExporterObsReport(configtelemetry.LevelNormal, exporter) + + errs := []error{nil, errFake} + toSendLogRecords := []int{17, 23} + for i, err := range errs { + ctx := obsrep.StartLogsExportOp(exporterCtx) + assert.NotNil(t, ctx) + + obsrep.EndLogsExportOp(ctx, toSendLogRecords[i], err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(errs), len(spans)) + + var sentLogRecords, failedToSendLogRecords int + for i, span := range spans { + assert.Equal(t, "exporter/"+exporter+"/logs", span.Name) + switch errs[i] { + case nil: + sentLogRecords += toSendLogRecords[i] + assert.Equal(t, int64(toSendLogRecords[i]), span.Attributes[obsreport.SentLogRecordsKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.FailedToSendLogRecordsKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + failedToSendLogRecords += toSendLogRecords[i] + assert.Equal(t, int64(0), span.Attributes[obsreport.SentLogRecordsKey]) + assert.Equal(t, int64(toSendLogRecords[i]), span.Attributes[obsreport.FailedToSendLogRecordsKey]) + assert.Equal(t, errs[i].Error(), span.Status.Message) + default: + t.Fatalf("unexpected error: %v", errs[i]) + } + } + + obsreporttest.CheckExporterLogsViews(t, exporter, int64(sentLogRecords), int64(failedToSendLogRecords)) +} + +func TestReceiveWithLongLivedCtx(t *testing.T) { + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + trace.ApplyConfig(trace.Config{ + DefaultSampler: trace.AlwaysSample(), + }) + defer func() { + trace.ApplyConfig(trace.Config{ + DefaultSampler: trace.ProbabilitySampler(1e-4), + }) + }() + + parentCtx, parentSpan := trace.StartSpan(context.Background(), t.Name()) + defer parentSpan.End() + + longLivedCtx := obsreport.ReceiverContext(parentCtx, receiver, transport) + ops := []struct { + numSpans int + err error + }{ + {numSpans: 13}, + {numSpans: 42, err: errFake}, + } + for _, op := range ops { + // Use a new context on each operation to simulate distinct operations + // under the same long lived context. + ctx := obsreport.StartTraceDataReceiveOp( + longLivedCtx, + receiver, + transport, + obsreport.WithLongLivedCtx()) + assert.NotNil(t, ctx) + + obsreport.EndTraceDataReceiveOp( + ctx, + format, + op.numSpans, + op.err) + } + + spans := ss.PullAllSpans() + require.Equal(t, len(ops), len(spans)) + + for i, span := range spans { + assert.Equal(t, trace.SpanID{}, span.ParentSpanID) + require.Equal(t, 1, len(span.Links)) + link := span.Links[0] + assert.Equal(t, trace.LinkTypeParent, link.Type) + assert.Equal(t, parentSpan.SpanContext().TraceID, link.TraceID) + assert.Equal(t, parentSpan.SpanContext().SpanID, link.SpanID) + assert.Equal(t, "receiver/"+receiver+"/TraceDataReceived", span.Name) + assert.Equal(t, transport, span.Attributes[obsreport.TransportKey]) + switch ops[i].err { + case nil: + assert.Equal(t, int64(ops[i].numSpans), span.Attributes[obsreport.AcceptedSpansKey]) + assert.Equal(t, int64(0), span.Attributes[obsreport.RefusedSpansKey]) + assert.Equal(t, trace.Status{Code: trace.StatusCodeOK}, span.Status) + case errFake: + assert.Equal(t, int64(0), span.Attributes[obsreport.AcceptedSpansKey]) + assert.Equal(t, int64(ops[i].numSpans), span.Attributes[obsreport.RefusedSpansKey]) + assert.Equal(t, ops[i].err.Error(), span.Status.Message) + default: + t.Fatalf("unexpected error: %v", ops[i].err) + } + } +} + +func TestProcessorTraceData(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + const acceptedSpans = 27 + const refusedSpans = 19 + const droppedSpans = 13 + + obsrep := obsreport.NewProcessorObsReport(configtelemetry.LevelNormal, processor) + obsrep.TracesAccepted(context.Background(), acceptedSpans) + obsrep.TracesRefused(context.Background(), refusedSpans) + obsrep.TracesDropped(context.Background(), droppedSpans) + + obsreporttest.CheckProcessorTracesViews(t, processor, acceptedSpans, refusedSpans, droppedSpans) +} + +func TestProcessorMetricsData(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + const acceptedPoints = 29 + const refusedPoints = 11 + const droppedPoints = 17 + + obsrep := obsreport.NewProcessorObsReport(configtelemetry.LevelNormal, processor) + obsrep.MetricsAccepted(context.Background(), acceptedPoints) + obsrep.MetricsRefused(context.Background(), refusedPoints) + obsrep.MetricsDropped(context.Background(), droppedPoints) + + obsreporttest.CheckProcessorMetricsViews(t, processor, acceptedPoints, refusedPoints, droppedPoints) +} + +func TestProcessorMetricViews(t *testing.T) { + measures := []stats.Measure{ + stats.Int64("firstMeasure", "test firstMeasure", stats.UnitDimensionless), + stats.Int64("secondMeasure", "test secondMeasure", stats.UnitBytes), + } + legacyViews := []*view.View{ + { + Name: measures[0].Name(), + Description: measures[0].Description(), + Measure: measures[0], + Aggregation: view.Sum(), + }, + { + Measure: measures[1], + Aggregation: view.Count(), + }, + } + + tests := []struct { + name string + level configtelemetry.Level + want []*view.View + }{ + { + name: "none", + level: configtelemetry.LevelNone, + }, + { + name: "basic", + level: configtelemetry.LevelBasic, + want: []*view.View{ + { + Name: "processor/test_type/" + measures[0].Name(), + Description: measures[0].Description(), + Measure: measures[0], + Aggregation: view.Sum(), + }, + { + Name: "processor/test_type/" + measures[1].Name(), + Measure: measures[1], + Aggregation: view.Count(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + obsreport.Configure(tt.level) + got := obsreport.ProcessorMetricViews("test_type", legacyViews) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestProcessorLogRecords(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + const acceptedRecords = 29 + const refusedRecords = 11 + const droppedRecords = 17 + + obsrep := obsreport.NewProcessorObsReport(configtelemetry.LevelNormal, processor) + obsrep.LogsAccepted(context.Background(), acceptedRecords) + obsrep.LogsRefused(context.Background(), refusedRecords) + obsrep.LogsDropped(context.Background(), droppedRecords) + + obsreporttest.CheckProcessorLogsViews(t, processor, acceptedRecords, refusedRecords, droppedRecords) +} + +type spanStore struct { + sync.Mutex + spans []*trace.SpanData +} + +func (ss *spanStore) ExportSpan(sd *trace.SpanData) { + ss.Lock() + ss.spans = append(ss.spans, sd) + ss.Unlock() +} + +func (ss *spanStore) PullAllSpans() []*trace.SpanData { + ss.Lock() + capturedSpans := ss.spans + ss.spans = nil + ss.Unlock() + return capturedSpans +} diff --git a/internal/otel_collector/obsreport/obsreporttest/obsreporttest.go b/internal/otel_collector/obsreport/obsreporttest/obsreporttest.go new file mode 100644 index 00000000000..b1e1a413ff6 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreporttest/obsreporttest.go @@ -0,0 +1,205 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreporttest + +import ( + "reflect" + "sort" + "testing" + + "github.com/stretchr/testify/require" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/obsreport" +) + +var ( + // Names used by the metrics and labels are hard coded here in order to avoid + // inadvertent changes: at this point changing metric names and labels should + // be treated as a breaking changing and requires a good justification. + // Changes to metric names or labels can break alerting, dashboards, etc + // that are used to monitor the Collector in production deployments. + // DO NOT SWITCH THE VARIABLES BELOW TO SIMILAR ONES DEFINED ON THE PACKAGE. + receiverTag, _ = tag.NewKey("receiver") + scraperTag, _ = tag.NewKey("scraper") + transportTag, _ = tag.NewKey("transport") + exporterTag, _ = tag.NewKey("exporter") + processorTag, _ = tag.NewKey("processor") +) + +// SetupRecordedMetricsTest does setup the testing environment to check the metrics recorded by receivers, producers or exporters. +// The returned function should be deferred. +func SetupRecordedMetricsTest() (func(), error) { + views := obsreport.Configure(configtelemetry.LevelNormal) + err := view.Register(views...) + if err != nil { + return nil, err + } + + return func() { + view.Unregister(views...) + }, err +} + +// CheckExporterTracesViews checks that for the current exported values for trace exporter views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckExporterTracesViews(t *testing.T, exporter string, acceptedSpans, droppedSpans int64) { + exporterTags := tagsForExporterView(exporter) + CheckValueForView(t, exporterTags, acceptedSpans, "exporter/sent_spans") + CheckValueForView(t, exporterTags, droppedSpans, "exporter/send_failed_spans") +} + +// CheckExporterMetricsViews checks that for the current exported values for metrics exporter views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckExporterMetricsViews(t *testing.T, exporter string, acceptedMetricsPoints, droppedMetricsPoints int64) { + exporterTags := tagsForExporterView(exporter) + CheckValueForView(t, exporterTags, acceptedMetricsPoints, "exporter/sent_metric_points") + CheckValueForView(t, exporterTags, droppedMetricsPoints, "exporter/send_failed_metric_points") +} + +// CheckExporterLogsViews checks that for the current exported values for logs exporter views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckExporterLogsViews(t *testing.T, exporter string, acceptedLogRecords, droppedLogRecords int64) { + exporterTags := tagsForExporterView(exporter) + CheckValueForView(t, exporterTags, acceptedLogRecords, "exporter/sent_log_records") + CheckValueForView(t, exporterTags, droppedLogRecords, "exporter/send_failed_log_records") +} + +// CheckProcessorTracesViews checks that for the current exported values for trace exporter views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckProcessorTracesViews(t *testing.T, processor string, acceptedSpans, refusedSpans, droppedSpans int64) { + processorTags := tagsForProcessorView(processor) + CheckValueForView(t, processorTags, acceptedSpans, "processor/accepted_spans") + CheckValueForView(t, processorTags, refusedSpans, "processor/refused_spans") + CheckValueForView(t, processorTags, droppedSpans, "processor/dropped_spans") +} + +// CheckProcessorMetricsViews checks that for the current exported values for metrics exporter views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckProcessorMetricsViews(t *testing.T, processor string, acceptedMetricPoints, refusedMetricPoints, droppedMetricPoints int64) { + processorTags := tagsForProcessorView(processor) + CheckValueForView(t, processorTags, acceptedMetricPoints, "processor/accepted_metric_points") + CheckValueForView(t, processorTags, refusedMetricPoints, "processor/refused_metric_points") + CheckValueForView(t, processorTags, droppedMetricPoints, "processor/dropped_metric_points") +} + +// CheckProcessorLogsViews checks that for the current exported values for logs exporter views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckProcessorLogsViews(t *testing.T, processor string, acceptedLogRecords, refusedLogRecords, droppedLogRecords int64) { + processorTags := tagsForProcessorView(processor) + CheckValueForView(t, processorTags, acceptedLogRecords, "processor/accepted_log_records") + CheckValueForView(t, processorTags, refusedLogRecords, "processor/refused_log_records") + CheckValueForView(t, processorTags, droppedLogRecords, "processor/dropped_log_records") +} + +// CheckReceiverTracesViews checks that for the current exported values for trace receiver views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckReceiverTracesViews(t *testing.T, receiver, protocol string, acceptedSpans, droppedSpans int64) { + receiverTags := tagsForReceiverView(receiver, protocol) + CheckValueForView(t, receiverTags, acceptedSpans, "receiver/accepted_spans") + CheckValueForView(t, receiverTags, droppedSpans, "receiver/refused_spans") +} + +// CheckReceiverLogsViews checks that for the current exported values for logs receiver views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckReceiverLogsViews(t *testing.T, receiver, protocol string, acceptedLogRecords, droppedLogRecords int64) { + receiverTags := tagsForReceiverView(receiver, protocol) + CheckValueForView(t, receiverTags, acceptedLogRecords, "receiver/accepted_log_records") + CheckValueForView(t, receiverTags, droppedLogRecords, "receiver/refused_log_records") +} + +// CheckReceiverMetricsViews checks that for the current exported values for metrics receiver views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckReceiverMetricsViews(t *testing.T, receiver, protocol string, acceptedMetricPoints, droppedMetricPoints int64) { + receiverTags := tagsForReceiverView(receiver, protocol) + CheckValueForView(t, receiverTags, acceptedMetricPoints, "receiver/accepted_metric_points") + CheckValueForView(t, receiverTags, droppedMetricPoints, "receiver/refused_metric_points") +} + +// CheckScraperMetricsViews checks that for the current exported values for metrics scraper views match given values. +// When this function is called it is required to also call SetupRecordedMetricsTest as first thing. +func CheckScraperMetricsViews(t *testing.T, receiver, scraper string, scrapedMetricPoints, erroredMetricPoints int64) { + scraperTags := tagsForScraperView(receiver, scraper) + CheckValueForView(t, scraperTags, scrapedMetricPoints, "scraper/scraped_metric_points") + CheckValueForView(t, scraperTags, erroredMetricPoints, "scraper/errored_metric_points") +} + +// CheckValueForView checks that for the current exported value in the view with the given name +// for {LegacyTagKeyReceiver: receiverName} is equal to "value". +func CheckValueForView(t *testing.T, wantTags []tag.Tag, value int64, vName string) { + // Make sure the tags slice is sorted by tag keys. + sortTags(wantTags) + + rows, err := view.RetrieveData(vName) + require.NoError(t, err) + + for _, row := range rows { + // Make sure the tags slice is sorted by tag keys. + sortTags(row.Tags) + if reflect.DeepEqual(wantTags, row.Tags) { + sum := row.Data.(*view.SumData) + require.Equal(t, float64(value), sum.Value) + return + } + } + + require.Failf(t, "could not find tags", "wantTags: %s in rows %v", wantTags, rows) +} + +// tagsForReceiverView returns the tags that are needed for the receiver views. +func tagsForReceiverView(receiver, transport string) []tag.Tag { + tags := make([]tag.Tag, 0, 2) + + tags = append(tags, tag.Tag{Key: receiverTag, Value: receiver}) + if transport != "" { + tags = append(tags, tag.Tag{Key: transportTag, Value: transport}) + } + + return tags +} + +// tagsForScraperView returns the tags that are needed for the scraper views. +func tagsForScraperView(receiver, scraper string) []tag.Tag { + tags := make([]tag.Tag, 0, 2) + + tags = append(tags, tag.Tag{Key: receiverTag, Value: receiver}) + if scraper != "" { + tags = append(tags, tag.Tag{Key: scraperTag, Value: scraper}) + } + + return tags +} + +// tagsForProcessorView returns the tags that are needed for the processor views. +func tagsForProcessorView(processor string) []tag.Tag { + return []tag.Tag{ + {Key: processorTag, Value: processor}, + } +} + +// tagsForExporterView returns the tags that are needed for the exporter views. +func tagsForExporterView(exporter string) []tag.Tag { + return []tag.Tag{ + {Key: exporterTag, Value: exporter}, + } +} + +func sortTags(tags []tag.Tag) { + sort.SliceStable(tags, func(i, j int) bool { + return tags[i].Key.Name() < tags[j].Key.Name() + }) +} diff --git a/internal/otel_collector/obsreport/obsreporttest/obsreporttest_test.go b/internal/otel_collector/obsreport/obsreporttest/obsreporttest_test.go new file mode 100644 index 00000000000..cb8262e7b31 --- /dev/null +++ b/internal/otel_collector/obsreport/obsreporttest/obsreporttest_test.go @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package obsreporttest_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +const ( + exporter = "fakeExporter" + receiver = "fakeReicever" + transport = "fakeTransport" + format = "fakeFormat" +) + +func TestCheckReceiverTracesViews(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + receiverCtx := obsreport.ReceiverContext(context.Background(), receiver, transport) + ctx := obsreport.StartTraceDataReceiveOp(receiverCtx, receiver, transport) + assert.NotNil(t, ctx) + obsreport.EndTraceDataReceiveOp( + ctx, + format, + 7, + nil) + + obsreporttest.CheckReceiverTracesViews(t, receiver, transport, 7, 0) +} + +func TestCheckReceiverMetricsViews(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + receiverCtx := obsreport.ReceiverContext(context.Background(), receiver, transport) + ctx := obsreport.StartMetricsReceiveOp(receiverCtx, receiver, transport) + assert.NotNil(t, ctx) + obsreport.EndMetricsReceiveOp(ctx, format, 7, nil) + + obsreporttest.CheckReceiverMetricsViews(t, receiver, transport, 7, 0) +} + +func TestCheckReceiverLogsViews(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + receiverCtx := obsreport.ReceiverContext(context.Background(), receiver, transport) + ctx := obsreport.StartLogsReceiveOp(receiverCtx, receiver, transport) + assert.NotNil(t, ctx) + obsreport.EndLogsReceiveOp(ctx, format, 7, nil) + + obsreporttest.CheckReceiverLogsViews(t, receiver, transport, 7, 0) +} + +func TestCheckExporterTracesViews(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + obsrep := obsreport.NewExporterObsReport(configtelemetry.LevelNormal, exporter) + exporterCtx := obsreport.ExporterContext(context.Background(), exporter) + ctx := obsrep.StartTracesExportOp(exporterCtx) + assert.NotNil(t, ctx) + + obsrep.EndTracesExportOp(ctx, 7, nil) + + obsreporttest.CheckExporterTracesViews(t, exporter, 7, 0) +} + +func TestCheckExporterMetricsViews(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + obsrep := obsreport.NewExporterObsReport(configtelemetry.LevelNormal, exporter) + exporterCtx := obsreport.ExporterContext(context.Background(), exporter) + ctx := obsrep.StartMetricsExportOp(exporterCtx) + assert.NotNil(t, ctx) + + obsrep.EndMetricsExportOp(ctx, 7, nil) + + obsreporttest.CheckExporterMetricsViews(t, exporter, 7, 0) +} + +func TestCheckExporterLogsViews(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + obsrep := obsreport.NewExporterObsReport(configtelemetry.LevelNormal, exporter) + exporterCtx := obsreport.ExporterContext(context.Background(), exporter) + ctx := obsrep.StartLogsExportOp(exporterCtx) + assert.NotNil(t, ctx) + obsrep.EndLogsExportOp(ctx, 7, nil) + + obsreporttest.CheckExporterLogsViews(t, exporter, 7, 0) +} diff --git a/internal/otel_collector/processor/README.md b/internal/otel_collector/processor/README.md new file mode 100644 index 00000000000..081f35a98e0 --- /dev/null +++ b/internal/otel_collector/processor/README.md @@ -0,0 +1,234 @@ +# General Information + +Processors are used at various stages of a pipeline. Generally, a processor +pre-processes data before it is exported (e.g. modify attributes or sample) or +helps ensure that data makes it through a pipeline successfully (e.g. +batch/retry). + +Some important aspects of pipelines and processors to be aware of: +- [Recommended Processors](#recommended-processors) +- [Data Ownership](#data-ownership) +- [Exclusive Ownership](#exclusive-ownership) +- [Shared Ownership](#shared-ownership) +- [Ordering Processors](#ordering-processors) + +Supported processors (sorted alphabetically): +- [Attributes Processor](attributesprocessor/README.md) +- [Batch Processor](batchprocessor/README.md) +- [Filter Processor](filterprocessor/README.md) +- [Memory Limiter Processor](memorylimiter/README.md) +- [Queued Retry Processor](queuedprocessor/README.md) +- [Resource Processor](resourceprocessor/README.md) +- [Probabilistic Sampling Processor](samplingprocessor/probabilisticsamplerprocessor/README.md) +- [Span Processor](spanprocessor/README.md) + +The [contributors repository](https://github.com/open-telemetry/opentelemetry-collector-contrib) + has more processors that can be added to custom builds of the Collector. + +## Recommended Processors + +No processors are enabled by default, however multiple processors are +recommended to be enabled depending on the data source. Processors must be +enabled for every data source and not all processors support all data sources. +In addition, it is important to note that the order of processors matters. The +order in each section below is the best practice. Refer to the individual +processor documentation for more information. + +### Traces + +1. [memory_limiter](memorylimiter/README.md) +2. *any sampling processors* +3. [batch](batchprocessor/README.md) +4. *any other processors* + +### Metrics + +1. [memory_limiter](memorylimiter/README.md) +2. [batch](batchprocessor/README.md) +3. *any other processors* + +## Data Ownership + +The ownership of the `TraceData` and `MetricsData` in a pipeline is passed as the data travels +through the pipeline. The data is created by the receiver and then the ownership is passed +to the first processor when `ConsumeTraceData`/`ConsumeMetricsData` function is called. + +Note: the receiver may be attached to multiple pipelines, in which case the same data +will be passed to all attached pipelines via a data fan-out connector. + +From data ownership perspective pipelines can work in 2 modes: +* Exclusive data ownership +* Shared data ownership + +The mode is defined during startup based on data modification intent reported by the +processors. The intent is reported by each processor via `MutatesConsumedData` field of +the struct returned by `GetCapabilities` function. If any processor in the pipeline +declares an intent to modify the data then that pipeline will work in exclusive ownership +mode. In addition, any other pipeline that receives data from a receiver that is attached +to a pipeline with exclusive ownership mode will be also operating in exclusive ownership +mode. + +### Exclusive Ownership + +In exclusive ownership mode the data is owned exclusively by a particular processor at a +given moment of time and the processor is free to modify the data it owns. + +Exclusive ownership mode is only applicable for pipelines that receive data from the +same receiver. If a pipeline is marked to be in exclusive ownership mode then any data +received from a shared receiver will be cloned at the fan-out connector before passing +further to each pipeline. This ensures that each pipeline has its own exclusive copy of +data and the data can be safely modified in the pipeline. + +The exclusive ownership of data allows processors to freely modify the data while +they own it (e.g. see `attributesprocessor`). The duration of ownership of the data +by processor is from the beginning of `ConsumeTraceData`/`ConsumeMetricsData` call +until the processor calls the next processor's `ConsumeTraceData`/`ConsumeMetricsData` +function, which passes the ownership to the next processor. After that the processor +must no longer read or write the data since it may be concurrently modified by the +new owner. + +Exclusive Ownership mode allows to easily implement processors that need to modify +the data by simply declaring such intent. + +### Shared Ownership + +In shared ownership mode no particular processor owns the data and no processor is +allowed the modify the shared data. + +In this mode no cloning is performed at the fan-out connector of receivers that +are attached to multiple pipelines. In this case all such pipelines will see +the same single shared copy of the data. Processors in pipelines operating in shared +ownership mode are prohibited from modifying the original data that they receive +via `ConsumeTraceData`/`ConsumeMetricsData` call. Processors may only read the data but +must not modify the data. + +If the processor needs to modify the data while performing the processing but +does not want to incur the cost of data cloning that Exclusive mode brings then +the processor can declare that it does not modify the data and use any +different technique that ensures original data is not modified. For example, +the processor can implement copy-on-write approach for individual sub-parts of +`TraceData`/`MetricsData` argument. Any approach that does not mutate the +original `TraceData`/`MetricsData` argument (including referenced data, such as +`Node`, `Resource`, `Spans`, etc) is allowed. + +If the processor uses such technique it should declare that it does not intend +to modify the original data by setting `MutatesConsumedData=false` in its capabilities +to avoid marking the pipeline for Exclusive ownership and to avoid the cost of +data cloning described in Exclusive Ownership section. + +## Ordering Processors + +The order processors are specified in a pipeline is important as this is the +order in which each processor is applied to traces and metrics. + +### Include/Exclude Metrics + +The [filter processor](filterprocessor/README.md) exposes the option to provide a set of +metric names to match against to determine if the metric should be +included or excluded from the processor. To configure this option, under +`include` and/or `exclude` both `match_type` and `metrics_names` are required. + +Note: If both `include` and `exclude` are specified, the `include` properties +are checked before the `exclude` properties. + +```yaml +filter: + # metrics indicates this processor applies to metrics + metrics: + # include and/or exclude can be specified. However, the include properties + # are always checked before the exclude properties. + {include, exclude}: + # match_type controls how items matching is done. + # Possible values are "regexp" or "strict". + # This is a required field. + match_type: {strict, regexp} + + # regexp is an optional configuration section for match_type regexp. + regexp: + # < see "Match Configuration" below > + + # metric_names specify an array of items to match the metric name against. + # This is a required field. + metric_names: [, ..., ] +``` + +#### Match Configuration + +Some `match_type` values have additional configuration options that can be +specified. The `match_type` value is the name of the configuration section. +These sections are optional. + +```yaml +# regexp is an optional configuration section for match_type regexp. +regexp: + # cacheenabled determines whether match results are LRU cached to make subsequent matches faster. + # Cache size is unlimited unless cachemaxnumentries is also specified. + cacheenabled: + # cachemaxnumentries is the max number of entries of the LRU cache; ignored if cacheenabled is false. + cachemaxnumentries: +``` + +### Include/Exclude Spans + +The [attribute processor](attributesprocessor/README.md) and the [span processor](spanprocessor/README.md) expose +the option to provide a set of properties of a span to match against to determine +if the span should be included or excluded from the processor. To configure +this option, under `include` and/or `exclude` at least `match_type` and one of +`services`, `span_names` or `attributes` is required. + +Note: If both `include` and `exclude` are specified, the `include` properties +are checked before the `exclude` properties. + +```yaml +{span, attributes}: + # include and/or exclude can be specified. However, the include properties + # are always checked before the exclude properties. + {include, exclude}: + # At least one of services, span_names or attributes must be specified. + # It is supported to have more than one specified, but all of the specified + # conditions must evaluate to true for a match to occur. + + # match_type controls how items in "services" and "span_names" arrays are + # interpreted. Possible values are "regexp" or "strict". + # This is a required field. + match_type: {strict, regexp} + + # regexp is an optional configuration section for match_type regexp. + regexp: + # < see "Match Configuration" below > + + # services specify an array of items to match the service name against. + # A match occurs if the span service name matches at least of the items. + # This is an optional field. + services: [, ..., ] + + # The span name must match at least one of the items. + # This is an optional field. + span_names: [, ..., ] + + # Attributes specifies the list of attributes to match against. + # All of these attributes must match exactly for a match to occur. + # This is an optional field. + attributes: + # Key specifies the attribute to match against. + - key: + # Value specifies the exact value to match against. + # If not specified, a match occurs if the key is present in the attributes. + value: {value} +``` + +#### Match Configuration + +Some `match_type` values have additional configuration options that can be +specified. The `match_type` value is the name of the configuration section. +These sections are optional. + +```yaml +# regexp is an optional configuration section for match_type regexp. +regexp: + # cacheenabled determines whether match results are LRU cached to make subsequent matches faster. + # Cache size is unlimited unless cachemaxnumentries is also specified. + cacheenabled: + # cachemaxnumentries is the max number of entries of the LRU cache; ignored if cacheenabled is false. + cachemaxnumentries: +``` diff --git a/internal/otel_collector/processor/attributesprocessor/README.md b/internal/otel_collector/processor/attributesprocessor/README.md new file mode 100644 index 00000000000..36b612c4acc --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/README.md @@ -0,0 +1,106 @@ +# Attributes Processor + +Supported pipeline types: traces + +The attributes processor modifies attributes of a span. Please refer to +[config.go](./config.go) for the config spec. + +It optionally supports the ability to [include/exclude spans](../README.md#includeexclude-spans). + +It takes a list of actions which are performed in order specified in the config. +The supported actions are: +- `insert`: Inserts a new attribute in spans where the key does not already exist. +- `update`: Updates an attribute in spans where the key does exist. +- `upsert`: Performs insert or update. Inserts a new attribute in spans where the + key does not already exist and updates an attribute in spans where the key + does exist. +- `delete`: Deletes an attribute from a span. +- `hash`: Hashes (SHA1) an existing attribute value. +- `extract`: Extracts values using a regular expression rule from the input key + to target keys specified in the rule. If a target key already exists, it will + be overridden. Note: It behaves similar to the Span Processor `to_attributes` + setting with the existing attribute as the source. + +For the actions `insert`, `update` and `upsert`, + - `key` is required + - one of `value` or `from_attribute` is required + - `action` is required. +```yaml + # Key specifies the attribute to act upon. +- key: + action: {insert, update, upsert} + # Value specifies the value to populate for the key. + # The type is inferred from the configuration. + value: + + # Key specifies the attribute to act upon. +- key: + action: {insert, update, upsert} + # FromAttribute specifies the attribute from the span to use to populate + # the value. If the attribute doesn't exist, no action is performed. + from_attribute: +``` + +For the `delete` action, + - `key` is required + - `action: delete` is required. +```yaml +# Key specifies the attribute to act upon. +- key: + action: delete +``` + + +For the `hash` action, + - `key` is required + - `action: hash` is required. +```yaml +# Key specifies the attribute to act upon. +- key: + action: hash +``` + + +For the `extract` action, + - `key` is required + - `pattern` is required. + ```yaml + # Key specifies the attribute to extract values from. + # The value of `key` is NOT altered. +- key: + # Rule specifies the regex pattern used to extract attributes from the value + # of `key`. + # The submatchers must be named. + # If attributes already exist, they will be overwritten. + pattern: + action: extract + + ``` + +The list of actions can be composed to create rich scenarios, such as +back filling attribute, copying values to a new key, redacting sensitive information. +The following is a sample configuration. + +```yaml +processors: + attributes/example: + actions: + - key: db.table + action: delete + - key: redacted_span + value: true + action: upsert + - key: copy_key + from_attribute: key_original + action: update + - key: account_id + value: 2245 + - key: account_password + action: delete + - key: account_email + action: hash + +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. diff --git a/internal/otel_collector/processor/attributesprocessor/attributes_log.go b/internal/otel_collector/processor/attributesprocessor/attributes_log.go new file mode 100644 index 00000000000..83b5174ab0d --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/attributes_log.go @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterlog" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +type logAttributesProcessor struct { + attrProc *processorhelper.AttrProc + include filterlog.Matcher + exclude filterlog.Matcher +} + +// newLogAttributesProcessor returns a processor that modifies attributes of a +// log record. To construct the attributes processors, the use of the factory +// methods are required in order to validate the inputs. +func newLogAttributesProcessor(attrProc *processorhelper.AttrProc, include, exclude filterlog.Matcher) *logAttributesProcessor { + return &logAttributesProcessor{ + attrProc: attrProc, + include: include, + exclude: exclude, + } +} + +// ProcessLogs implements the LogsProcessor +func (a *logAttributesProcessor) ProcessLogs(_ context.Context, ld pdata.Logs) (pdata.Logs, error) { + rls := ld.ResourceLogs() + for i := 0; i < rls.Len(); i++ { + rs := rls.At(i) + ilss := rs.InstrumentationLibraryLogs() + resource := rs.Resource() + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + logs := ils.Logs() + library := ils.InstrumentationLibrary() + for k := 0; k < logs.Len(); k++ { + lr := logs.At(k) + if a.skipLog(lr, resource, library) { + continue + } + + a.attrProc.Process(lr.Attributes()) + } + } + } + return ld, nil +} + +// skipLog determines if a log should be processed. +// True is returned when a log should be skipped. +// False is returned when a log should not be skipped. +// The logic determining if a log should be processed is set +// in the attribute configuration with the include and exclude settings. +// Include properties are checked before exclude settings are checked. +func (a *logAttributesProcessor) skipLog(lr pdata.LogRecord, resource pdata.Resource, library pdata.InstrumentationLibrary) bool { + if a.include != nil { + // A false returned in this case means the log should not be processed. + if include := a.include.MatchLogRecord(lr, resource, library); !include { + return true + } + } + + if a.exclude != nil { + // A true returned in this case means the log should not be processed. + if exclude := a.exclude.MatchLogRecord(lr, resource, library); exclude { + return true + } + } + + return false +} diff --git a/internal/otel_collector/processor/attributesprocessor/attributes_log_test.go b/internal/otel_collector/processor/attributesprocessor/attributes_log_test.go new file mode 100644 index 00000000000..2bf0afe50a7 --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/attributes_log_test.go @@ -0,0 +1,423 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +// Common structure for all the Tests +type logTestCase struct { + name string + inputAttributes map[string]pdata.AttributeValue + expectedAttributes map[string]pdata.AttributeValue +} + +// runIndividualLogTestCase is the common logic of passing trace data through a configured attributes processor. +func runIndividualLogTestCase(t *testing.T, tt logTestCase, tp component.LogsProcessor) { + t.Run(tt.name, func(t *testing.T) { + ld := generateLogData(tt.name, tt.inputAttributes) + assert.NoError(t, tp.ConsumeLogs(context.Background(), ld)) + // Ensure that the modified `ld` has the attributes sorted: + sortLogAttributes(ld) + require.Equal(t, generateLogData(tt.name, tt.expectedAttributes), ld) + }) +} + +func generateLogData(logName string, attrs map[string]pdata.AttributeValue) pdata.Logs { + td := pdata.NewLogs() + td.ResourceLogs().Resize(1) + rs := td.ResourceLogs().At(0) + rs.InstrumentationLibraryLogs().Resize(1) + ils := rs.InstrumentationLibraryLogs().At(0) + lrs := ils.Logs() + lrs.Resize(1) + lrs.At(0).SetName(logName) + lrs.At(0).Attributes().InitFromMap(attrs).Sort() + return td +} + +func sortLogAttributes(ld pdata.Logs) { + rss := ld.ResourceLogs() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + rs.Resource().Attributes().Sort() + ilss := rs.InstrumentationLibraryLogs() + for j := 0; j < ilss.Len(); j++ { + logs := ilss.At(j).Logs() + for k := 0; k < logs.Len(); k++ { + s := logs.At(k) + s.Attributes().Sort() + } + } + } +} + +// TestLogProcessor_Values tests all possible value types. +func TestLogProcessor_NilEmptyData(t *testing.T) { + type nilEmptyTestCase struct { + name string + input pdata.Logs + output pdata.Logs + } + testCases := []nilEmptyTestCase{ + { + name: "empty", + input: testdata.GenerateLogDataEmpty(), + output: testdata.GenerateLogDataEmpty(), + }, + { + name: "one-empty-resource-logs", + input: testdata.GenerateLogDataOneEmptyResourceLogs(), + output: testdata.GenerateLogDataOneEmptyResourceLogs(), + }, + { + name: "no-libraries", + input: testdata.GenerateLogDataOneEmptyResourceLogs(), + output: testdata.GenerateLogDataOneEmptyResourceLogs(), + }, + } + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Settings.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + {Key: "attribute1", Action: processorhelper.DELETE}, + } + + tp, err := factory.CreateLogsProcessor( + context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewLogsNop()) + require.Nil(t, err) + require.NotNil(t, tp) + for i := range testCases { + tt := testCases[i] + t.Run(tt.name, func(t *testing.T) { + assert.NoError(t, tp.ConsumeLogs(context.Background(), tt.input)) + assert.EqualValues(t, tt.output, tt.input) + }) + } +} + +func TestAttributes_FilterLogs(t *testing.T) { + testCases := []logTestCase{ + { + name: "apply processor", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply processor with different value for exclude property", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "incorrect name for include property", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "attribute match for exclude property", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + LogNames: []string{"^[^i].*"}, + Config: *createConfig(filterset.Regexp), + } + oCfg.Exclude = &filterconfig.MatchProperties{ + Attributes: []filterconfig.Attribute{ + {Key: "NoModification", Value: true}, + }, + Config: *createConfig(filterset.Strict), + } + tp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualLogTestCase(t, tt, tp) + } +} + +func TestAttributes_FilterLogsByNameStrict(t *testing.T) { + testCases := []logTestCase{ + { + name: "apply", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "incorrect_log_name", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "dont_apply", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "incorrect_log_name_with_attr", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + LogNames: []string{"apply", "dont_apply"}, + Config: *createConfig(filterset.Strict), + } + oCfg.Exclude = &filterconfig.MatchProperties{ + LogNames: []string{"dont_apply"}, + Config: *createConfig(filterset.Strict), + } + tp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualLogTestCase(t, tt, tp) + } +} + +func TestAttributes_FilterLogsByNameRegexp(t *testing.T) { + testCases := []logTestCase{ + { + name: "apply_to_log_with_no_attrs", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply_to_log_with_attr", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "incorrect_log_name", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "apply_dont_apply", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "incorrect_log_name_with_attr", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + LogNames: []string{"^apply.*"}, + Config: *createConfig(filterset.Regexp), + } + oCfg.Exclude = &filterconfig.MatchProperties{ + LogNames: []string{".*dont_apply$"}, + Config: *createConfig(filterset.Regexp), + } + tp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualLogTestCase(t, tt, tp) + } +} + +func TestLogAttributes_Hash(t *testing.T) { + testCases := []logTestCase{ + { + name: "String", + inputAttributes: map[string]pdata.AttributeValue{ + "user.email": pdata.NewAttributeValueString("john.doe@example.com"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.email": pdata.NewAttributeValueString("73ec53c4ba1747d485ae2a0d7bfafa6cda80a5a9"), + }, + }, + { + name: "Int", + inputAttributes: map[string]pdata.AttributeValue{ + "user.id": pdata.NewAttributeValueInt(10), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.id": pdata.NewAttributeValueString("71aa908aff1548c8c6cdecf63545261584738a25"), + }, + }, + { + name: "Double", + inputAttributes: map[string]pdata.AttributeValue{ + "user.balance": pdata.NewAttributeValueDouble(99.1), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.balance": pdata.NewAttributeValueString("76429edab4855b03073f9429fd5d10313c28655e"), + }, + }, + { + name: "Bool", + inputAttributes: map[string]pdata.AttributeValue{ + "user.authenticated": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.authenticated": pdata.NewAttributeValueString("bf8b4530d8d246dd74ac53a13471bba17941dff7"), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "user.email", Action: processorhelper.HASH}, + {Key: "user.id", Action: processorhelper.HASH}, + {Key: "user.balance", Action: processorhelper.HASH}, + {Key: "user.authenticated", Action: processorhelper.HASH}, + } + + tp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualLogTestCase(t, tt, tp) + } +} + +func BenchmarkAttributes_FilterLogsByName(b *testing.B) { + testCases := []logTestCase{ + { + name: "apply_to_log_with_no_attrs", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply_to_log_with_attr", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "dont_apply", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + LogNames: []string{"^apply.*"}, + } + tp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + require.Nil(b, err) + require.NotNil(b, tp) + + for _, tt := range testCases { + td := generateLogData(tt.name, tt.inputAttributes) + + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + assert.NoError(b, tp.ConsumeLogs(context.Background(), td)) + } + }) + + // Ensure that the modified `td` has the attributes sorted: + sortLogAttributes(td) + require.Equal(b, generateLogData(tt.name, tt.expectedAttributes), td) + } +} diff --git a/internal/otel_collector/processor/attributesprocessor/attributes_trace.go b/internal/otel_collector/processor/attributesprocessor/attributes_trace.go new file mode 100644 index 00000000000..0e708fe55be --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/attributes_trace.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterspan" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +type spanAttributesProcessor struct { + attrProc *processorhelper.AttrProc + include filterspan.Matcher + exclude filterspan.Matcher +} + +// newTraceProcessor returns a processor that modifies attributes of a span. +// To construct the attributes processors, the use of the factory methods are required +// in order to validate the inputs. +func newSpanAttributesProcessor(attrProc *processorhelper.AttrProc, include, exclude filterspan.Matcher) *spanAttributesProcessor { + return &spanAttributesProcessor{ + attrProc: attrProc, + include: include, + exclude: exclude, + } +} + +// ProcessTraces implements the TProcessor +func (a *spanAttributesProcessor) ProcessTraces(_ context.Context, td pdata.Traces) (pdata.Traces, error) { + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + resource := rs.Resource() + ilss := rs.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + spans := ils.Spans() + library := ils.InstrumentationLibrary() + for k := 0; k < spans.Len(); k++ { + span := spans.At(k) + if filterspan.SkipSpan(a.include, a.exclude, span, resource, library) { + continue + } + + a.attrProc.Process(span.Attributes()) + } + } + } + return td, nil +} diff --git a/internal/otel_collector/processor/attributesprocessor/attributes_trace_test.go b/internal/otel_collector/processor/attributesprocessor/attributes_trace_test.go new file mode 100644 index 00000000000..a6eaf7f7506 --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/attributes_trace_test.go @@ -0,0 +1,452 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/processor/processorhelper" + "go.opentelemetry.io/collector/translator/conventions" +) + +// Common structure for all the Tests +type testCase struct { + name string + serviceName string + inputAttributes map[string]pdata.AttributeValue + expectedAttributes map[string]pdata.AttributeValue +} + +// runIndividualTestCase is the common logic of passing trace data through a configured attributes processor. +func runIndividualTestCase(t *testing.T, tt testCase, tp component.TracesProcessor) { + t.Run(tt.name, func(t *testing.T) { + td := generateTraceData(tt.serviceName, tt.name, tt.inputAttributes) + assert.NoError(t, tp.ConsumeTraces(context.Background(), td)) + // Ensure that the modified `td` has the attributes sorted: + sortAttributes(td) + require.Equal(t, generateTraceData(tt.serviceName, tt.name, tt.expectedAttributes), td) + }) +} + +func generateTraceData(serviceName, spanName string, attrs map[string]pdata.AttributeValue) pdata.Traces { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + rs := td.ResourceSpans().At(0) + if serviceName != "" { + rs.Resource().Attributes().UpsertString(conventions.AttributeServiceName, serviceName) + } + rs.InstrumentationLibrarySpans().Resize(1) + ils := rs.InstrumentationLibrarySpans().At(0) + spans := ils.Spans() + spans.Resize(1) + spans.At(0).SetName(spanName) + spans.At(0).Attributes().InitFromMap(attrs).Sort() + return td +} + +func sortAttributes(td pdata.Traces) { + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + rs.Resource().Attributes().Sort() + ilss := rs.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + spans := ilss.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + spans.At(k).Attributes().Sort() + } + } + } +} + +// TestSpanProcessor_Values tests all possible value types. +func TestSpanProcessor_NilEmptyData(t *testing.T) { + type nilEmptyTestCase struct { + name string + input pdata.Traces + output pdata.Traces + } + // TODO: Add test for "nil" Span/Attributes. This needs support from data slices to allow to construct that. + testCases := []nilEmptyTestCase{ + { + name: "empty", + input: testdata.GenerateTraceDataEmpty(), + output: testdata.GenerateTraceDataEmpty(), + }, + { + name: "one-empty-resource-spans", + input: testdata.GenerateTraceDataOneEmptyResourceSpans(), + output: testdata.GenerateTraceDataOneEmptyResourceSpans(), + }, + { + name: "no-libraries", + input: testdata.GenerateTraceDataNoLibraries(), + output: testdata.GenerateTraceDataNoLibraries(), + }, + { + name: "one-empty-instrumentation-library", + input: testdata.GenerateTraceDataOneEmptyInstrumentationLibrary(), + output: testdata.GenerateTraceDataOneEmptyInstrumentationLibrary(), + }, + } + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Settings.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + {Key: "attribute1", Action: processorhelper.DELETE}, + } + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + for i := range testCases { + tt := testCases[i] + t.Run(tt.name, func(t *testing.T) { + assert.NoError(t, tp.ConsumeTraces(context.Background(), tt.input)) + assert.EqualValues(t, tt.output, tt.input) + }) + } +} + +func TestAttributes_FilterSpans(t *testing.T) { + testCases := []testCase{ + { + name: "apply processor", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply processor with different value for exclude property", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "incorrect name for include property", + serviceName: "noname", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "attribute match for exclude property", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + Services: []string{"svcA", "svcB.*"}, + Config: *createConfig(filterset.Regexp), + } + oCfg.Exclude = &filterconfig.MatchProperties{ + Attributes: []filterconfig.Attribute{ + {Key: "NoModification", Value: true}, + }, + Config: *createConfig(filterset.Strict), + } + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, tp) + } +} + +func TestAttributes_FilterSpansByNameStrict(t *testing.T) { + testCases := []testCase{ + { + name: "apply", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "incorrect_span_name", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "dont_apply", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "incorrect_span_name_with_attr", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + SpanNames: []string{"apply", "dont_apply"}, + Config: *createConfig(filterset.Strict), + } + oCfg.Exclude = &filterconfig.MatchProperties{ + SpanNames: []string{"dont_apply"}, + Config: *createConfig(filterset.Strict), + } + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, tp) + } +} + +func TestAttributes_FilterSpansByNameRegexp(t *testing.T) { + testCases := []testCase{ + { + name: "apply_to_span_with_no_attrs", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply_to_span_with_attr", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "incorrect_span_name", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "apply_dont_apply", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + { + name: "incorrect_span_name_with_attr", + serviceName: "svcB", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(true), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + SpanNames: []string{"^apply.*"}, + Config: *createConfig(filterset.Regexp), + } + oCfg.Exclude = &filterconfig.MatchProperties{ + SpanNames: []string{".*dont_apply$"}, + Config: *createConfig(filterset.Regexp), + } + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, tp) + } +} + +func TestAttributes_Hash(t *testing.T) { + testCases := []testCase{ + { + name: "String", + inputAttributes: map[string]pdata.AttributeValue{ + "user.email": pdata.NewAttributeValueString("john.doe@example.com"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.email": pdata.NewAttributeValueString("73ec53c4ba1747d485ae2a0d7bfafa6cda80a5a9"), + }, + }, + { + name: "Int", + inputAttributes: map[string]pdata.AttributeValue{ + "user.id": pdata.NewAttributeValueInt(10), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.id": pdata.NewAttributeValueString("71aa908aff1548c8c6cdecf63545261584738a25"), + }, + }, + { + name: "Double", + inputAttributes: map[string]pdata.AttributeValue{ + "user.balance": pdata.NewAttributeValueDouble(99.1), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.balance": pdata.NewAttributeValueString("76429edab4855b03073f9429fd5d10313c28655e"), + }, + }, + { + name: "Bool", + inputAttributes: map[string]pdata.AttributeValue{ + "user.authenticated": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user.authenticated": pdata.NewAttributeValueString("bf8b4530d8d246dd74ac53a13471bba17941dff7"), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "user.email", Action: processorhelper.HASH}, + {Key: "user.id", Action: processorhelper.HASH}, + {Key: "user.balance", Action: processorhelper.HASH}, + {Key: "user.authenticated", Action: processorhelper.HASH}, + } + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, tp) + } +} + +func BenchmarkAttributes_FilterSpansByName(b *testing.B) { + testCases := []testCase{ + { + name: "apply_to_span_with_no_attrs", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + { + name: "apply_to_span_with_attr", + inputAttributes: map[string]pdata.AttributeValue{ + "NoModification": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + "NoModification": pdata.NewAttributeValueBool(false), + }, + }, + { + name: "dont_apply", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "attribute1", Action: processorhelper.INSERT, Value: 123}, + } + oCfg.Include = &filterconfig.MatchProperties{ + SpanNames: []string{"^apply.*"}, + } + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + require.Nil(b, err) + require.NotNil(b, tp) + + for _, tt := range testCases { + td := generateTraceData(tt.serviceName, tt.name, tt.inputAttributes) + + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + assert.NoError(b, tp.ConsumeTraces(context.Background(), td)) + } + }) + + // Ensure that the modified `td` has the attributes sorted: + sortAttributes(td) + require.Equal(b, generateTraceData(tt.serviceName, tt.name, tt.expectedAttributes), td) + } +} + +func createConfig(matchType filterset.MatchType) *filterset.Config { + return &filterset.Config{ + MatchType: matchType, + } +} diff --git a/internal/otel_collector/processor/attributesprocessor/config.go b/internal/otel_collector/processor/attributesprocessor/config.go new file mode 100644 index 00000000000..6e4b10b3bd4 --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/config.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +// Config specifies the set of attributes to be inserted, updated, upserted and +// deleted and the properties to include/exclude a span from being processed. +// This processor handles all forms of modifications to attributes within a span. +// Prior to any actions being applied, each span is compared against +// the include properties and then the exclude properties if they are specified. +// This determines if a span is to be processed or not. +// The list of actions is applied in order specified in the configuration. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + + filterconfig.MatchConfig `mapstructure:",squash"` + + // Specifies the list of attributes to act on. + // The set of actions are {INSERT, UPDATE, UPSERT, DELETE, HASH, EXTRACT}. + // This is a required field. + processorhelper.Settings `mapstructure:",squash"` +} diff --git a/internal/otel_collector/processor/attributesprocessor/config_test.go b/internal/otel_collector/processor/attributesprocessor/config_test.go new file mode 100644 index 00000000000..c4bf7ad9b49 --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/config_test.go @@ -0,0 +1,237 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +func TestLoadingConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + assert.NoError(t, err) + require.NotNil(t, cfg) + + p0 := cfg.Processors["attributes/insert"] + assert.Equal(t, p0, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/insert", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "attribute1", Value: 123, Action: processorhelper.INSERT}, + {Key: "string key", FromAttribute: "anotherkey", Action: processorhelper.INSERT}, + }, + }, + }) + + p1 := cfg.Processors["attributes/update"] + assert.Equal(t, p1, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/update", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "boo", FromAttribute: "foo", Action: processorhelper.UPDATE}, + {Key: "db.secret", Value: "redacted", Action: processorhelper.UPDATE}, + }, + }, + }) + + p2 := cfg.Processors["attributes/upsert"] + assert.Equal(t, p2, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/upsert", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "region", Value: "planet-earth", Action: processorhelper.UPSERT}, + {Key: "new_user_key", FromAttribute: "user_key", Action: processorhelper.UPSERT}, + }, + }, + }) + + p3 := cfg.Processors["attributes/delete"] + assert.Equal(t, p3, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/delete", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "credit_card", Action: processorhelper.DELETE}, + {Key: "duplicate_key", Action: processorhelper.DELETE}, + }, + }, + }) + + p4 := cfg.Processors["attributes/hash"] + assert.Equal(t, p4, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/hash", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "user.email", Action: processorhelper.HASH}, + }, + }, + }) + + p5 := cfg.Processors["attributes/excludemulti"] + assert.Equal(t, p5, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/excludemulti", + TypeVal: typeStr, + }, + MatchConfig: filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"svcA", "svcB"}, + Attributes: []filterconfig.Attribute{ + {Key: "env", Value: "dev"}, + {Key: "test_request"}, + }, + }, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "credit_card", Action: processorhelper.DELETE}, + {Key: "duplicate_key", Action: processorhelper.DELETE}, + }, + }, + }) + + p6 := cfg.Processors["attributes/includeservices"] + assert.Equal(t, p6, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/includeservices", + TypeVal: typeStr, + }, + MatchConfig: filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{"auth.*", "login.*"}, + }, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "credit_card", Action: processorhelper.DELETE}, + {Key: "duplicate_key", Action: processorhelper.DELETE}, + }, + }, + }) + + p7 := cfg.Processors["attributes/selectiveprocessing"] + assert.Equal(t, p7, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/selectiveprocessing", + TypeVal: typeStr, + }, + MatchConfig: filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Services: []string{"svcA", "svcB"}, + }, + Exclude: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Attributes: []filterconfig.Attribute{ + {Key: "redact_trace", Value: false}, + }, + }, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "credit_card", Action: processorhelper.DELETE}, + {Key: "duplicate_key", Action: processorhelper.DELETE}, + }, + }, + }) + + p8 := cfg.Processors["attributes/complex"] + assert.Equal(t, p8, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/complex", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "operation", Value: "default", Action: processorhelper.INSERT}, + {Key: "svc.operation", FromAttribute: "operation", Action: processorhelper.UPSERT}, + {Key: "operation", Action: processorhelper.DELETE}, + }, + }, + }) + + p9 := cfg.Processors["attributes/example"] + assert.Equal(t, p9, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/example", + TypeVal: typeStr, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "db.table", Action: processorhelper.DELETE}, + {Key: "redacted_span", Value: true, Action: processorhelper.UPSERT}, + {Key: "copy_key", FromAttribute: "key_original", Action: processorhelper.UPDATE}, + {Key: "account_id", Value: 2245, Action: processorhelper.INSERT}, + {Key: "account_password", Action: processorhelper.DELETE}, + }, + }, + }) + + p10 := cfg.Processors["attributes/regexp"] + assert.Equal(t, p10, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "attributes/regexp", + TypeVal: typeStr, + }, + MatchConfig: filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Services: []string{"auth.*"}, + }, + Exclude: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanNames: []string{"login.*"}, + }, + }, + Settings: processorhelper.Settings{ + Actions: []processorhelper.ActionKeyValue{ + {Key: "password", Action: processorhelper.UPDATE, Value: "obfuscated"}, + {Key: "token", Action: processorhelper.DELETE}, + }, + }, + }) + +} diff --git a/internal/otel_collector/processor/attributesprocessor/doc.go b/internal/otel_collector/processor/attributesprocessor/doc.go new file mode 100644 index 00000000000..6f8455a1722 --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package attributesprocessor contains the logic to modify attributes of a span. +// It supports insert, update, upsert and delete as actions. +package attributesprocessor diff --git a/internal/otel_collector/processor/attributesprocessor/factory.go b/internal/otel_collector/processor/attributesprocessor/factory.go new file mode 100644 index 00000000000..c0a30538004 --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/factory.go @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "context" + "fmt" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/internal/processor/filterlog" + "go.opentelemetry.io/collector/internal/processor/filterspan" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // typeStr is the value of "type" key in configuration. + typeStr = "attributes" +) + +var processorCapabilities = component.ProcessorCapabilities{MutatesConsumedData: true} + +// NewFactory returns a new factory for the Attributes processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor), + processorhelper.WithLogs(createLogProcessor)) +} + +// Note: This isn't a valid configuration because the processor would do no work. +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createTraceProcessor( + _ context.Context, + _ component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, +) (component.TracesProcessor, error) { + oCfg := cfg.(*Config) + if len(oCfg.Actions) == 0 { + return nil, fmt.Errorf("error creating \"attributes\" processor due to missing required field \"actions\" of processor %q", cfg.Name()) + } + attrProc, err := processorhelper.NewAttrProc(&oCfg.Settings) + if err != nil { + return nil, fmt.Errorf("error creating \"attributes\" processor: %w of processor %q", err, cfg.Name()) + } + include, err := filterspan.NewMatcher(oCfg.Include) + if err != nil { + return nil, err + } + exclude, err := filterspan.NewMatcher(oCfg.Exclude) + if err != nil { + return nil, err + } + + return processorhelper.NewTraceProcessor( + cfg, + nextConsumer, + newSpanAttributesProcessor(attrProc, include, exclude), + processorhelper.WithCapabilities(processorCapabilities)) +} + +func createLogProcessor( + _ context.Context, + _ component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.LogsConsumer, +) (component.LogsProcessor, error) { + oCfg := cfg.(*Config) + if len(oCfg.Actions) == 0 { + return nil, fmt.Errorf("error creating \"attributes\" processor due to missing required field \"actions\" of processor %q", cfg.Name()) + } + attrProc, err := processorhelper.NewAttrProc(&oCfg.Settings) + if err != nil { + return nil, fmt.Errorf("error creating \"attributes\" processor: %w of processor %q", err, cfg.Name()) + } + include, err := filterlog.NewMatcher(oCfg.Include) + if err != nil { + return nil, err + } + exclude, err := filterlog.NewMatcher(oCfg.Exclude) + if err != nil { + return nil, err + } + + return processorhelper.NewLogsProcessor( + cfg, + nextConsumer, + newLogAttributesProcessor(attrProc, include, exclude), + processorhelper.WithCapabilities(processorCapabilities)) +} diff --git a/internal/otel_collector/processor/attributesprocessor/factory_test.go b/internal/otel_collector/processor/attributesprocessor/factory_test.go new file mode 100644 index 00000000000..a8fa6b9549f --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/factory_test.go @@ -0,0 +1,149 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributesprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +func TestFactory_Type(t *testing.T) { + factory := NewFactory() + assert.Equal(t, factory.Type(), configmodels.Type(typeStr)) +} + +func TestFactory_CreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.Equal(t, cfg, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + }) + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestFactoryCreateTraceProcessor_EmptyActions(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + ap, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + assert.Error(t, err) + assert.Nil(t, ap) +} + +func TestFactoryCreateTraceProcessor_InvalidActions(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + // Missing key + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "", Value: 123, Action: processorhelper.UPSERT}, + } + ap, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + assert.Error(t, err) + assert.Nil(t, ap) +} + +func TestFactoryCreateTraceProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "a key", Action: processorhelper.DELETE}, + } + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + assert.NotNil(t, tp) + assert.NoError(t, err) + + tp, err = factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, nil) + assert.Nil(t, tp) + assert.Error(t, err) + + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Action: processorhelper.DELETE}, + } + tp, err = factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + assert.Nil(t, tp) + assert.Error(t, err) +} + +func TestFactory_CreateMetricsProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + mp, err := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, nil) + require.Nil(t, mp) + assert.Equal(t, err, configerror.ErrDataTypeIsNotSupported) +} + +func TestFactoryCreateLogsProcessor_EmptyActions(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + ap, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + assert.Error(t, err) + assert.Nil(t, ap) +} + +func TestFactoryCreateLogsProcessor_InvalidActions(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + // Missing key + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "", Value: 123, Action: processorhelper.UPSERT}, + } + ap, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + assert.Error(t, err) + assert.Nil(t, ap) +} + +func TestFactoryCreateLogsProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Key: "a key", Action: processorhelper.DELETE}, + } + + tp, err := factory.CreateLogsProcessor( + context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + assert.NotNil(t, tp) + assert.NoError(t, err) + + tp, err = factory.CreateLogsProcessor( + context.Background(), component.ProcessorCreateParams{}, cfg, nil) + assert.Nil(t, tp) + assert.Error(t, err) + + oCfg.Actions = []processorhelper.ActionKeyValue{ + {Action: processorhelper.DELETE}, + } + tp, err = factory.CreateLogsProcessor( + context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewLogsNop()) + assert.Nil(t, tp) + assert.Error(t, err) +} diff --git a/internal/otel_collector/processor/attributesprocessor/testdata/config.yaml b/internal/otel_collector/processor/attributesprocessor/testdata/config.yaml new file mode 100644 index 00000000000..8525f0be36e --- /dev/null +++ b/internal/otel_collector/processor/attributesprocessor/testdata/config.yaml @@ -0,0 +1,316 @@ +processors: + # The following example demonstrates inserting keys/values into spans. + attributes/insert: + actions: + # The following inserts a new attribute {"attribute1": 123} to spans where + # the key "attribute1" doesn't exist. + # The type of `attribute1` is inferred by the configuration. + # `123` is an integer and is stored as an integer in the attributes. + # This demonstrates how to backfill spans with an attribute that may + # not have been sent by all clients. + - key: "attribute1" + value: 123 + action: insert + # The following uses the value from attribute "anotherkey" to insert a new + # attribute {"string key": } to spans + # where the key "string key" does not exist. If the attribute "anotherkey" + # doesn't exist, no new attribute is inserted to spans. + - key: "string key" + from_attribute: "anotherkey" + action: insert + + # The following example demonstrates using regex to create new attributes + # based on the value of another attribute. + attributes/regex_insert: + actions: + # The following uses the value from `key:http.url` to upsert attributes + # to the target keys specified in the `rule`. + # (Insert attributes for target keys that do not exist and update keys + # that exist.) + # Given http.url = http://example.com/path?queryParam1=value1,queryParam2=value2 + # then the following attributes will be inserted: + # http_protocol: http + # http_domain: example.com + # http_path: path + # http_query_params=queryParam1=value1,queryParam2=value2 + # http.url value does NOT change. + # Note: Similar to the Span Procesor, if a target key already exists, + # it will be updated. + - key: "http.url" + pattern: ^(?P.*):\/\/(?P.*)\/(?P.*)(\?|\&)(?P.*) + action: extract + + # The following demonstrates configuring the processor to only update existing + # keys in an attribute. + # Note: `action: update` must be set. + attributes/update: + actions: + # The following updates the attribute 'boo' using the value from attribute + # 'foo'. Spans without the attribute 'boo' will not change. + - key: "boo" + from_attribute: "foo" + action: update + # The following updates the attribute to { "db.secret": "redacted"}. + # This demonstrates sanitizing spans of sensitive data. + - key: db.secret + value: redacted + action: update + + # The following demonstrates setting an attribute on both spans where the + # key does exist and the key doesn't exist. + attributes/upsert: + actions: + # The following demonstrates how to set an attribute on all spans. + # Any spans that already had `region` now have value `planet-earth`. + # This can be done to set properties for all traces without + # requiring an instrumentation change. + - key: region + value: "planet-earth" + action: upsert + + # The following demonstrates copying a value to a new key. + # Note: If a span doesn't contain `user_key`, no new attribute `new_user_key` + # is created. + - key: new_user_key + from_attribute: user_key + action: upsert + + # The following demonstrates deleting keys from an attribute. + attributes/delete: + actions: + - key: credit_card + action: delete + - key: duplicate_key + action: delete + + # The following demonstrates hash existing attribute values. + attributes/hash: + actions: + - key: user.email + action: hash + + + # The following demonstrates excluding spans from this attributes processor. + # Ex. The following spans match the properties and won't be processed by the + # processor. + # Span1 Name: 'svcB' Attributes: {env: dev, test_request: 123, credit_card: 1234} + # Span2 Name: 'svcA' Attributes: {env: dev, test_request: false} + # The following spans do not match the properties and the processor actions + # are applied to it. + # Span3 Name: 'svcB' Attributes: {env: 1, test_request: dev, credit_card: 1234} + # Span4 Name: 'svcC' Attributes: {env: dev, test_request: false} + attributes/excludemulti: + # Specifies the spans properties that exclude a span from being processed. + exclude: + # match_type defines that "services" is an array of strings that must + # match service name strictly. + match_type: strict + # The Span service name must be equal to "svcA" or "svcB". + services: ["svcA", "svcB"] + attributes: + # This exact attribute ('env', 'dev') must exist in the span for a match. + - {key: env, value: "dev"} + # As long as there is an attribute with key 'test_request' in the span + # there is a match. + - {key: test_request} + actions: + - key: credit_card + action: delete + - key: duplicate_key + action: delete + + # The following demonstrates excluding spans from this attributes processor based on a resource. + attributes/excluderesources: + # Specifies the spans properties that exclude a span from being processed. + exclude: + # match_type defines that "resources" is an map where values must match strictly. + match_type: strict + resources: + # This exact resource ('host.type', 'n1-standard-1') must exist in the span for a match. + - {key: host.type, value: "n1-standard-1"} + actions: + - key: credit_card + action: delete + - key: duplicate_key + action: delete + + # The following demonstrates excluding spans from this attributes processor based on an instrumenting library. + # If no version is provided, any version will match, even no version. + # If a blank version provided, only no version will match. + attributes/excludelibrary: + # Specifies the spans properties that exclude a span from being processed. + exclude: + # match_type defines that "libraries" is an map where values must match strictly. + match_type: strict + libraries: + # This exact library ('mongo-java-driver', version '3.8.0') must exist in the span for a match. + - {name: "mongo-java-driver", version: "3.8.0"} + actions: + - key: credit_card + action: delete + - key: duplicate_key + action: delete + + # The following demonstrates including spans for this attributes processor. + # All other spans that do no match the properties are not processed + # by this processor. + # Ex. The following are spans match the properties and the actions are applied. + # Span1 Name: 'svcB' Attributes: {env: dev, test_request: 123, credit_card: 1234} + # Span2 Name: 'svcA' Attributes: {env: dev, test_request: false} + # Span3 Name: 'svcB' Attributes: {env: 1, test_request: dev, credit_card: 1234} + # The following span does not match the include properties and the + # processor actions are not applied. + # Span4 Name: 'svcC' Attributes: {env: dev, test_request: false} + attributes/includeservices: + # Specifies the span properties that must exist for the processor to be applied. + include: + # match_type defines that "services" is an array of regexp-es. + match_type: regexp + # The Span service name must match "auth.*" or "login.*" regexp. + services: ["auth.*", "login.*"] + actions: + - key: credit_card + action: delete + - key: duplicate_key + action: delete + + # The following demonstrates specifying the set of span properties to + # indicate which spans this processor should be applied to. The `include` of + # properties say which ones should be included and the `exclude` properties + # further filter out spans that shouldn't be processed. + # Ex. The following are spans match the properties and the actions are applied. + # Note this span is processed because the value type of redact_trace is a string instead of a boolean. + # Span1 Name: 'svcB' Attributes: {env: production, test_request: 123, credit_card: 1234, redact_trace: "false"} + # Span2 Name: 'svcA' Attributes: {env: staging, test_request: false, redact_trace: true} + # The following span does not match the include properties and the + # processor actions are not applied. + # Span3 Name: 'svcB' Attributes: {env: production, test_request: true, credit_card: 1234, redact_trace: false} + # Span4 Name: 'svcC' Attributes: {env: dev, test_request: false} + attributes/selectiveprocessing: + # Specifies the span properties that must exist for the processor to be applied. + include: + # match_type defines that "services" is an array of strings that must + # match service name strictly. + match_type: strict + # The Span service name must be equal to "svcA" or "svcB". + services: ["svcA", "svcB"] + exclude: + # match_type defines that "attributes" values must match strictly. + match_type: strict + attributes: + - { key: redact_trace, value: false} + actions: + - key: credit_card + action: delete + - key: duplicate_key + action: delete + + # The following demonstrates how to backfill spans missing an attribute, + # insert/update that value to a new key and deleting the old key. This guarantees + # an attribute `svc.operation` exists in spans and the attribute `operation` + # doesn't exist. + # Ex: The spans before the processor `attributes/complex`. + # Span1 Attributes: {timeout: 10, svc.operation: addition, operation: addition} + # Span2 Attributes: {operation: subtract, math_value: 123} + # Span3 Attributes: {timeout: 10, math_value: 4} + # Span4 Attributes: {svc.operation: division, timeout: 3} + attributes/complex: + # Note: There are no include and exclude settings so all spans are processed. + actions: + - key: operation + value: default + action: insert + # The spans after the first action of insert. + # Span1 Attributes: {timeout: 10, svc.operation: addition, operation: addition} + # Span2 Attributes: {operation: subtract, math_value: 123} + # Span3 Attributes: {timeout: 10, math_value: 4, operation: default} + # Span4 Attributes: {svc.operation: division, timeout: 3, operation:default} + + - key: svc.operation + from_attribute: operation + action: upsert + # The spans after the second action of upsert. + # Span1 Attributes: {timeout: 10, svc.operation: addition, operation: addition} + # Span2 Attributes: {svc.operation: subtract, operation: subtract, math_value: 123} + # Span3 Attributes: {svc.operation: default, timeout: 10, math_value: 4, operation: default} + # Span4 Attributes: {svc.operation: default, timeout: 3, operation:default} + + - key: operation + action: delete + # The spans after the third/final action of delete. + # Span1 Attributes: {timeout: 10, svc.operation: addition} + # Span2 Attributes: {svc.operation: subtract, math_value: 123} + # Span3 Attributes: {svc.operation: default, timeout: 10, math_value: 4} + # Span4 Attributes: {svc.operation: default, timeout: 3} + + # The following is an example of various actions. The actions are applied in + # the order specified in the configuration. + attributes/example: + actions: + - key: db.table + action: delete + - key: redacted_span + value: true + action: upsert + - key: copy_key + from_attribute: key_original + action: update + - key: account_id + value: 2245 + action: insert + - key: account_password + action: delete + + # The following demonstrates how to process spans that have a service name and span + # name that match regexp patterns. This processor will remove "token" attribute + # and will obfuscate "password" attribute in spans where service name matches "auth.*" + # and where span name does not match "login.*". + attributes/regexp: + # Specifies the span properties that must exist for the processor to be applied. + include: + # match_type defines that "services" is an array of regexp-es. + match_type: regexp + # The span service name must match "auth.*" pattern. + services: ["auth.*"] + exclude: + # match_type defines that "span_names" is an array of regexp-es. + match_type: regexp + # The span name must not match "login.*" pattern. + span_names: ["login.*"] + actions: + - key: password + action: update + value: "obfuscated" + - key: token + action: delete + + # The following demonstrates how to process spans that have an attribute that matches a regexp patterns. + # This processor will obfuscate "db.statement" attribute in spans where "db.statement attribute + # matches a regex pattern. + attributes/regexp2: + # Specifies the span properties that must exist for the processor to be applied. + include: + # match_type defines that "attributes" is a map where values are regexp-es. + match_type: regexp + attributes: + # This attribute ('db.statement') must exist in the span and match the regex ('SELECT \* FROM USERS.*') for a match. + - {key: env, value: "'SELECT * FROM USERS WHERE ID=1'"} + actions: + - key: db.statement + action: update + value: "SELECT * FROM USERS [obfuscated]" + +receivers: + examplereceiver: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [attributes/insert] + exporters: [exampleexporter] + + diff --git a/internal/otel_collector/processor/batchprocessor/README.md b/internal/otel_collector/processor/batchprocessor/README.md new file mode 100644 index 00000000000..3ea4e354545 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/README.md @@ -0,0 +1,38 @@ +# Batch Processor + +Supported pipeline types: metric, traces, logs + +The batch processor accepts spans, metrics, or logs and places them into +batches. Batching helps better compress the data and reduce the number of +outgoing connections required to transmit the data. This processor supports +both size and time based batching. + +It is highly recommended to configure the batch processor on every collector. +The batch processor should be defined in the pipeline after the `memory_limiter` +as well as any sampling processors. This is because batching should happen after +any data drops such as sampling. + +Please refer to [config.go](./config.go) for the config spec. + +The following configuration options can be modified: +- `send_batch_size` (default = 8192): Number of spans or metrics after which a +batch will be sent. +- `timeout` (default = 200ms): Time duration after which a batch will be sent +regardless of size. +- `send_batch_max_size` (default = 0): The maximum number of items in a batch. + This property ensures that larger batches are split into smaller units. + By default (`0`), there is no upper limit of the batch size. + It is currently supported only for the trace pipeline. + +Examples: + +```yaml +processors: + batch: + batch/2: + send_batch_size: 10000 + timeout: 10s +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. diff --git a/internal/otel_collector/processor/batchprocessor/batch_processor.go b/internal/otel_collector/processor/batchprocessor/batch_processor.go new file mode 100644 index 00000000000..f43d8fd0c8a --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/batch_processor.go @@ -0,0 +1,348 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "context" + "runtime" + "time" + + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/processor" +) + +// batch_processor is a component that accepts spans and metrics, places them +// into batches and sends downstream. +// +// batch_processor implements consumer.TracesConsumer and consumer.MetricsConsumer +// +// Batches are sent out with any of the following conditions: +// - batch size reaches cfg.SendBatchSize +// - cfg.Timeout is elapsed since the timestamp when the previous batch was sent out. +type batchProcessor struct { + name string + logger *zap.Logger + telemetryLevel configtelemetry.Level + + sendBatchSize uint32 + timeout time.Duration + sendBatchMaxSize uint32 + + timer *time.Timer + done chan struct{} + newItem chan interface{} + batch batch + + ctx context.Context + cancel context.CancelFunc +} + +type batch interface { + // export the current batch + export(ctx context.Context) error + + // itemCount returns the size of the current batch + itemCount() uint32 + + // size returns the size in bytes of the current batch + size() int + + // reset the current batch structure with zero/empty values. + reset() + + // add item to the current batch + add(item interface{}) +} + +var _ consumer.TracesConsumer = (*batchProcessor)(nil) +var _ consumer.MetricsConsumer = (*batchProcessor)(nil) +var _ consumer.LogsConsumer = (*batchProcessor)(nil) + +func newBatchProcessor(params component.ProcessorCreateParams, cfg *Config, batch batch, telemetryLevel configtelemetry.Level) *batchProcessor { + ctx, cancel := context.WithCancel(context.Background()) + return &batchProcessor{ + name: cfg.Name(), + logger: params.Logger, + telemetryLevel: telemetryLevel, + + sendBatchSize: cfg.SendBatchSize, + sendBatchMaxSize: cfg.SendBatchMaxSize, + timeout: cfg.Timeout, + done: make(chan struct{}, 1), + newItem: make(chan interface{}, runtime.NumCPU()), + batch: batch, + ctx: ctx, + cancel: cancel, + } +} + +func (bp *batchProcessor) GetCapabilities() component.ProcessorCapabilities { + return component.ProcessorCapabilities{MutatesConsumedData: true} +} + +// Start is invoked during service startup. +func (bp *batchProcessor) Start(context.Context, component.Host) error { + go bp.startProcessingCycle() + return nil +} + +// Shutdown is invoked during service shutdown. +func (bp *batchProcessor) Shutdown(context.Context) error { + bp.cancel() + <-bp.done + return nil +} + +func (bp *batchProcessor) startProcessingCycle() { + bp.timer = time.NewTimer(bp.timeout) + for { + select { + case <-bp.ctx.Done(): + DONE: + for { + select { + case item := <-bp.newItem: + bp.processItem(item) + default: + break DONE + } + } + // This is the close of the channel + if bp.batch.itemCount() > 0 { + // TODO: Set a timeout on sendTraces or + // make it cancellable using the context that Shutdown gets as a parameter + bp.sendItems(statTimeoutTriggerSend) + } + close(bp.done) + return + case item := <-bp.newItem: + if item == nil { + continue + } + bp.processItem(item) + case <-bp.timer.C: + if bp.batch.itemCount() > 0 { + bp.sendItems(statTimeoutTriggerSend) + } + bp.resetTimer() + } + } +} + +func (bp *batchProcessor) processItem(item interface{}) { + if bp.sendBatchMaxSize > 0 { + if td, ok := item.(pdata.Traces); ok { + itemCount := bp.batch.itemCount() + if itemCount+uint32(td.SpanCount()) > bp.sendBatchMaxSize { + tdRemainSize := splitTrace(int(bp.sendBatchSize-itemCount), td) + item = tdRemainSize + go func() { + bp.newItem <- td + }() + } + } + } + + bp.batch.add(item) + if bp.batch.itemCount() >= bp.sendBatchSize { + bp.timer.Stop() + bp.sendItems(statBatchSizeTriggerSend) + bp.resetTimer() + } +} + +func (bp *batchProcessor) resetTimer() { + bp.timer.Reset(bp.timeout) +} + +func (bp *batchProcessor) sendItems(measure *stats.Int64Measure) { + // Add that it came form the trace pipeline? + statsTags := []tag.Mutator{tag.Insert(processor.TagProcessorNameKey, bp.name)} + _ = stats.RecordWithTags(context.Background(), statsTags, measure.M(1), statBatchSendSize.M(int64(bp.batch.itemCount()))) + + if bp.telemetryLevel == configtelemetry.LevelDetailed { + _ = stats.RecordWithTags(context.Background(), statsTags, statBatchSendSizeBytes.M(int64(bp.batch.size()))) + } + + if err := bp.batch.export(context.Background()); err != nil { + bp.logger.Warn("Sender failed", zap.Error(err)) + } + bp.batch.reset() +} + +// ConsumeTraces implements TracesProcessor +func (bp *batchProcessor) ConsumeTraces(_ context.Context, td pdata.Traces) error { + bp.newItem <- td + return nil +} + +// ConsumeTraces implements MetricsProcessor +func (bp *batchProcessor) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + // First thing is convert into a different internal format + bp.newItem <- md + return nil +} + +// ConsumeLogs implements LogsProcessor +func (bp *batchProcessor) ConsumeLogs(_ context.Context, ld pdata.Logs) error { + bp.newItem <- ld + return nil +} + +// newBatchTracesProcessor creates a new batch processor that batches traces by size or with timeout +func newBatchTracesProcessor(params component.ProcessorCreateParams, trace consumer.TracesConsumer, cfg *Config, telemetryLevel configtelemetry.Level) *batchProcessor { + return newBatchProcessor(params, cfg, newBatchTraces(trace), telemetryLevel) +} + +// newBatchMetricsProcessor creates a new batch processor that batches metrics by size or with timeout +func newBatchMetricsProcessor(params component.ProcessorCreateParams, metrics consumer.MetricsConsumer, cfg *Config, telemetryLevel configtelemetry.Level) *batchProcessor { + return newBatchProcessor(params, cfg, newBatchMetrics(metrics), telemetryLevel) +} + +// newBatchLogsProcessor creates a new batch processor that batches logs by size or with timeout +func newBatchLogsProcessor(params component.ProcessorCreateParams, logs consumer.LogsConsumer, cfg *Config, telemetryLevel configtelemetry.Level) *batchProcessor { + return newBatchProcessor(params, cfg, newBatchLogs(logs), telemetryLevel) +} + +type batchTraces struct { + nextConsumer consumer.TracesConsumer + traceData pdata.Traces + spanCount uint32 +} + +func newBatchTraces(nextConsumer consumer.TracesConsumer) *batchTraces { + b := &batchTraces{nextConsumer: nextConsumer} + b.reset() + return b +} + +// add updates current batchTraces by adding new TraceData object +func (bt *batchTraces) add(item interface{}) { + td := item.(pdata.Traces) + newSpanCount := td.SpanCount() + if newSpanCount == 0 { + return + } + + bt.spanCount += uint32(newSpanCount) + td.ResourceSpans().MoveAndAppendTo(bt.traceData.ResourceSpans()) +} + +func (bt *batchTraces) export(ctx context.Context) error { + return bt.nextConsumer.ConsumeTraces(ctx, bt.traceData) +} + +func (bt *batchTraces) itemCount() uint32 { + return bt.spanCount +} + +func (bt *batchTraces) size() int { + return bt.traceData.Size() +} + +// resets the current batchTraces structure with zero values +func (bt *batchTraces) reset() { + bt.traceData = pdata.NewTraces() + bt.spanCount = 0 +} + +type batchMetrics struct { + nextConsumer consumer.MetricsConsumer + metricData pdata.Metrics + metricCount uint32 +} + +func newBatchMetrics(nextConsumer consumer.MetricsConsumer) *batchMetrics { + b := &batchMetrics{nextConsumer: nextConsumer} + b.reset() + return b +} + +func (bm *batchMetrics) export(ctx context.Context) error { + return bm.nextConsumer.ConsumeMetrics(ctx, bm.metricData) +} + +func (bm *batchMetrics) itemCount() uint32 { + return bm.metricCount +} + +func (bm *batchMetrics) size() int { + return bm.metricData.Size() +} + +// resets the current batchMetrics structure with zero/empty values. +func (bm *batchMetrics) reset() { + bm.metricData = pdata.NewMetrics() + bm.metricCount = 0 +} + +func (bm *batchMetrics) add(item interface{}) { + md := item.(pdata.Metrics) + + newMetricsCount := md.MetricCount() + if newMetricsCount == 0 { + return + } + bm.metricCount += uint32(newMetricsCount) + md.ResourceMetrics().MoveAndAppendTo(bm.metricData.ResourceMetrics()) +} + +type batchLogs struct { + nextConsumer consumer.LogsConsumer + logData pdata.Logs + logCount uint32 +} + +func newBatchLogs(nextConsumer consumer.LogsConsumer) *batchLogs { + b := &batchLogs{nextConsumer: nextConsumer} + b.reset() + return b +} + +func (bm *batchLogs) export(ctx context.Context) error { + return bm.nextConsumer.ConsumeLogs(ctx, bm.logData) +} + +func (bm *batchLogs) itemCount() uint32 { + return bm.logCount +} + +func (bm *batchLogs) size() int { + return bm.logData.SizeBytes() +} + +// resets the current batchLogs structure with zero/empty values. +func (bm *batchLogs) reset() { + bm.logData = pdata.NewLogs() + bm.logCount = 0 +} + +func (bm *batchLogs) add(item interface{}) { + ld := item.(pdata.Logs) + + newLogsCount := ld.LogRecordCount() + if newLogsCount == 0 { + return + } + bm.logCount += uint32(newLogsCount) + ld.ResourceLogs().MoveAndAppendTo(bm.logData.ResourceLogs()) +} diff --git a/internal/otel_collector/processor/batchprocessor/batch_processor_test.go b/internal/otel_collector/processor/batchprocessor/batch_processor_test.go new file mode 100644 index 00000000000..b21bbe3dff8 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/batch_processor_test.go @@ -0,0 +1,691 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/stats/view" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestBatchProcessorSpansDelivered(t *testing.T) { + sink := new(consumertest.TracesSink) + cfg := createDefaultConfig().(*Config) + cfg.SendBatchSize = 128 + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchTracesProcessor(creationParams, sink, cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + requestCount := 1000 + spansPerRequest := 100 + traceDataSlice := make([]pdata.Traces, 0, requestCount) + for requestNum := 0; requestNum < requestCount; requestNum++ { + td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest) + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + for spanIndex := 0; spanIndex < spansPerRequest; spanIndex++ { + spans.At(spanIndex).SetName(getTestSpanName(requestNum, spanIndex)) + } + traceDataSlice = append(traceDataSlice, td.Clone()) + assert.NoError(t, batcher.ConsumeTraces(context.Background(), td)) + } + + // Added to test logic that check for empty resources. + td := testdata.GenerateTraceDataEmpty() + assert.NoError(t, batcher.ConsumeTraces(context.Background(), td)) + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*spansPerRequest, sink.SpansCount()) + receivedTraces := sink.AllTraces() + spansReceivedByName := spansReceivedByName(receivedTraces) + for requestNum := 0; requestNum < requestCount; requestNum++ { + spans := traceDataSlice[requestNum].ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + for spanIndex := 0; spanIndex < spansPerRequest; spanIndex++ { + require.EqualValues(t, + spans.At(spanIndex), + spansReceivedByName[getTestSpanName(requestNum, spanIndex)]) + } + } +} + +func TestBatchProcessorSpansDeliveredEnforceBatchSize(t *testing.T) { + sink := new(consumertest.TracesSink) + cfg := createDefaultConfig().(*Config) + cfg.SendBatchSize = 128 + cfg.SendBatchMaxSize = 128 + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchTracesProcessor(creationParams, sink, cfg, configtelemetry.LevelBasic) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + requestCount := 1000 + spansPerRequest := 150 + for requestNum := 0; requestNum < requestCount; requestNum++ { + td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest) + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + for spanIndex := 0; spanIndex < spansPerRequest; spanIndex++ { + spans.At(spanIndex).SetName(getTestSpanName(requestNum, spanIndex)) + } + assert.NoError(t, batcher.ConsumeTraces(context.Background(), td)) + } + + // Added to test logic that check for empty resources. + td := testdata.GenerateTraceDataEmpty() + batcher.ConsumeTraces(context.Background(), td) + + // wait for all spans to be reported + for { + if sink.SpansCount() == requestCount*spansPerRequest { + break + } + <-time.After(cfg.Timeout) + } + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*spansPerRequest, sink.SpansCount()) + for i := 0; i < len(sink.AllTraces())-1; i++ { + assert.Equal(t, cfg.SendBatchSize, uint32(sink.AllTraces()[i].SpanCount())) + } + // the last batch has the remaining size + assert.Equal(t, (requestCount*spansPerRequest)%int(cfg.SendBatchSize), sink.AllTraces()[len(sink.AllTraces())-1].SpanCount()) +} + +func TestBatchProcessorSentBySize(t *testing.T) { + views := MetricViews() + require.NoError(t, view.Register(views...)) + defer view.Unregister(views...) + + sink := new(consumertest.TracesSink) + cfg := createDefaultConfig().(*Config) + sendBatchSize := 20 + cfg.SendBatchSize = uint32(sendBatchSize) + cfg.Timeout = 500 * time.Millisecond + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchTracesProcessor(creationParams, sink, cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + requestCount := 100 + spansPerRequest := 5 + + start := time.Now() + sizeSum := 0 + for requestNum := 0; requestNum < requestCount; requestNum++ { + td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest) + sizeSum += td.Size() + assert.NoError(t, batcher.ConsumeTraces(context.Background(), td)) + } + + require.NoError(t, batcher.Shutdown(context.Background())) + + elapsed := time.Since(start) + require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds()) + + expectedBatchesNum := requestCount * spansPerRequest / sendBatchSize + expectedBatchingFactor := sendBatchSize / spansPerRequest + + require.Equal(t, requestCount*spansPerRequest, sink.SpansCount()) + receivedTraces := sink.AllTraces() + require.EqualValues(t, expectedBatchesNum, len(receivedTraces)) + for _, td := range receivedTraces { + rss := td.ResourceSpans() + require.Equal(t, expectedBatchingFactor, rss.Len()) + for i := 0; i < expectedBatchingFactor; i++ { + require.Equal(t, spansPerRequest, rss.At(i).InstrumentationLibrarySpans().At(0).Spans().Len()) + } + } + + viewData, err := view.RetrieveData("processor/batch/" + statBatchSendSize.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData := viewData[0].Data.(*view.DistributionData) + assert.Equal(t, int64(expectedBatchesNum), distData.Count) + assert.Equal(t, sink.SpansCount(), int(distData.Sum())) + assert.Equal(t, sendBatchSize, int(distData.Min)) + assert.Equal(t, sendBatchSize, int(distData.Max)) + + viewData, err = view.RetrieveData("processor/batch/" + statBatchSendSizeBytes.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData = viewData[0].Data.(*view.DistributionData) + assert.Equal(t, int64(expectedBatchesNum), distData.Count) + assert.Equal(t, sizeSum, int(distData.Sum())) +} + +func TestBatchProcessorSentByTimeout(t *testing.T) { + sink := new(consumertest.TracesSink) + cfg := createDefaultConfig().(*Config) + sendBatchSize := 100 + cfg.SendBatchSize = uint32(sendBatchSize) + cfg.Timeout = 100 * time.Millisecond + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + requestCount := 5 + spansPerRequest := 10 + start := time.Now() + + batcher := newBatchTracesProcessor(creationParams, sink, cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + for requestNum := 0; requestNum < requestCount; requestNum++ { + td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest) + assert.NoError(t, batcher.ConsumeTraces(context.Background(), td)) + } + + // Wait for at least one batch to be sent. + for { + if sink.SpansCount() != 0 { + break + } + <-time.After(cfg.Timeout) + } + + elapsed := time.Since(start) + require.LessOrEqual(t, cfg.Timeout.Nanoseconds(), elapsed.Nanoseconds()) + + // This should not change the results in the sink, verified by the expectedBatchesNum + require.NoError(t, batcher.Shutdown(context.Background())) + + expectedBatchesNum := 1 + expectedBatchingFactor := 5 + + require.Equal(t, requestCount*spansPerRequest, sink.SpansCount()) + receivedTraces := sink.AllTraces() + require.EqualValues(t, expectedBatchesNum, len(receivedTraces)) + for _, td := range receivedTraces { + rss := td.ResourceSpans() + require.Equal(t, expectedBatchingFactor, rss.Len()) + for i := 0; i < expectedBatchingFactor; i++ { + require.Equal(t, spansPerRequest, rss.At(i).InstrumentationLibrarySpans().At(0).Spans().Len()) + } + } +} + +func TestBatchProcessorTraceSendWhenClosing(t *testing.T) { + cfg := Config{ + Timeout: 3 * time.Second, + SendBatchSize: 1000, + } + sink := new(consumertest.TracesSink) + + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchTracesProcessor(creationParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + requestCount := 10 + spansPerRequest := 10 + for requestNum := 0; requestNum < requestCount; requestNum++ { + td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest) + assert.NoError(t, batcher.ConsumeTraces(context.Background(), td)) + } + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*spansPerRequest, sink.SpansCount()) + require.Equal(t, 1, len(sink.AllTraces())) +} + +func TestBatchMetricProcessor_ReceivingData(t *testing.T) { + // Instantiate the batch processor with low config values to test data + // gets sent through the processor. + cfg := Config{ + Timeout: 200 * time.Millisecond, + SendBatchSize: 50, + } + + requestCount := 100 + metricsPerRequest := 5 + sink := new(consumertest.MetricsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchMetricsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + metricDataSlice := make([]pdata.Metrics, 0, requestCount) + + for requestNum := 0; requestNum < requestCount; requestNum++ { + md := testdata.GenerateMetricsManyMetricsSameResource(metricsPerRequest) + metrics := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics() + for metricIndex := 0; metricIndex < metricsPerRequest; metricIndex++ { + metrics.At(metricIndex).SetName(getTestMetricName(requestNum, metricIndex)) + } + metricDataSlice = append(metricDataSlice, md.Clone()) + assert.NoError(t, batcher.ConsumeMetrics(context.Background(), md)) + } + + // Added to test case with empty resources sent. + md := testdata.GenerateMetricsEmpty() + assert.NoError(t, batcher.ConsumeMetrics(context.Background(), md)) + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*metricsPerRequest, sink.MetricsCount()) + receivedMds := sink.AllMetrics() + metricsReceivedByName := metricsReceivedByName(receivedMds) + for requestNum := 0; requestNum < requestCount; requestNum++ { + metrics := metricDataSlice[requestNum].ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics() + for metricIndex := 0; metricIndex < metricsPerRequest; metricIndex++ { + require.EqualValues(t, + metrics.At(metricIndex), + metricsReceivedByName[getTestMetricName(requestNum, metricIndex)]) + } + } +} + +func TestBatchMetricProcessor_BatchSize(t *testing.T) { + views := MetricViews() + require.NoError(t, view.Register(views...)) + defer view.Unregister(views...) + + // Instantiate the batch processor with low config values to test data + // gets sent through the processor. + cfg := Config{ + Timeout: 100 * time.Millisecond, + SendBatchSize: 50, + } + + requestCount := 100 + metricsPerRequest := 5 + sink := new(consumertest.MetricsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchMetricsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + start := time.Now() + size := 0 + for requestNum := 0; requestNum < requestCount; requestNum++ { + md := testdata.GenerateMetricsManyMetricsSameResource(metricsPerRequest) + size += md.Size() + assert.NoError(t, batcher.ConsumeMetrics(context.Background(), md)) + } + require.NoError(t, batcher.Shutdown(context.Background())) + + elapsed := time.Since(start) + require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds()) + + expectedBatchesNum := requestCount * metricsPerRequest / int(cfg.SendBatchSize) + expectedBatchingFactor := int(cfg.SendBatchSize) / metricsPerRequest + + require.Equal(t, requestCount*metricsPerRequest, sink.MetricsCount()) + receivedMds := sink.AllMetrics() + require.Equal(t, expectedBatchesNum, len(receivedMds)) + for _, md := range receivedMds { + require.Equal(t, expectedBatchingFactor, md.ResourceMetrics().Len()) + for i := 0; i < expectedBatchingFactor; i++ { + require.Equal(t, metricsPerRequest, md.ResourceMetrics().At(i).InstrumentationLibraryMetrics().At(0).Metrics().Len()) + } + } + + viewData, err := view.RetrieveData("processor/batch/" + statBatchSendSize.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData := viewData[0].Data.(*view.DistributionData) + assert.Equal(t, int64(expectedBatchesNum), distData.Count) + assert.Equal(t, sink.MetricsCount(), int(distData.Sum())) + assert.Equal(t, cfg.SendBatchSize, uint32(distData.Min)) + assert.Equal(t, cfg.SendBatchSize, uint32(distData.Max)) + + viewData, err = view.RetrieveData("processor/batch/" + statBatchSendSizeBytes.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData = viewData[0].Data.(*view.DistributionData) + assert.Equal(t, int64(expectedBatchesNum), distData.Count) + assert.Equal(t, size, int(distData.Sum())) +} + +func TestBatchMetricsProcessor_Timeout(t *testing.T) { + cfg := Config{ + Timeout: 100 * time.Millisecond, + SendBatchSize: 100, + } + requestCount := 5 + metricsPerRequest := 10 + sink := new(consumertest.MetricsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchMetricsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + start := time.Now() + for requestNum := 0; requestNum < requestCount; requestNum++ { + md := testdata.GenerateMetricsManyMetricsSameResource(metricsPerRequest) + assert.NoError(t, batcher.ConsumeMetrics(context.Background(), md)) + } + + // Wait for at least one batch to be sent. + for { + if sink.MetricsCount() != 0 { + break + } + <-time.After(cfg.Timeout) + } + + elapsed := time.Since(start) + require.LessOrEqual(t, cfg.Timeout.Nanoseconds(), elapsed.Nanoseconds()) + + // This should not change the results in the sink, verified by the expectedBatchesNum + require.NoError(t, batcher.Shutdown(context.Background())) + + expectedBatchesNum := 1 + expectedBatchingFactor := 5 + + require.Equal(t, requestCount*metricsPerRequest, sink.MetricsCount()) + receivedMds := sink.AllMetrics() + require.Equal(t, expectedBatchesNum, len(receivedMds)) + for _, md := range receivedMds { + require.Equal(t, expectedBatchingFactor, md.ResourceMetrics().Len()) + for i := 0; i < expectedBatchingFactor; i++ { + require.Equal(t, metricsPerRequest, md.ResourceMetrics().At(i).InstrumentationLibraryMetrics().At(0).Metrics().Len()) + } + } +} + +func TestBatchMetricProcessor_Shutdown(t *testing.T) { + cfg := Config{ + Timeout: 3 * time.Second, + SendBatchSize: 1000, + } + requestCount := 5 + metricsPerRequest := 10 + sink := new(consumertest.MetricsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchMetricsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + for requestNum := 0; requestNum < requestCount; requestNum++ { + md := testdata.GenerateMetricsManyMetricsSameResource(metricsPerRequest) + assert.NoError(t, batcher.ConsumeMetrics(context.Background(), md)) + } + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*metricsPerRequest, sink.MetricsCount()) + require.Equal(t, 1, len(sink.AllMetrics())) +} + +func getTestSpanName(requestNum, index int) string { + return fmt.Sprintf("test-span-%d-%d", requestNum, index) +} + +func spansReceivedByName(tds []pdata.Traces) map[string]pdata.Span { + spansReceivedByName := map[string]pdata.Span{} + for i := range tds { + rss := tds[i].ResourceSpans() + for i := 0; i < rss.Len(); i++ { + ilss := rss.At(i).InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + spans := ilss.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + span := spans.At(k) + spansReceivedByName[spans.At(k).Name()] = span + } + } + } + } + return spansReceivedByName +} + +func metricsReceivedByName(mds []pdata.Metrics) map[string]pdata.Metric { + metricsReceivedByName := map[string]pdata.Metric{} + for _, md := range mds { + rms := md.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + ilms := rms.At(i).InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + metrics := ilms.At(j).Metrics() + for k := 0; k < metrics.Len(); k++ { + metric := metrics.At(k) + metricsReceivedByName[metric.Name()] = metric + } + } + } + } + return metricsReceivedByName +} + +func getTestMetricName(requestNum, index int) string { + return fmt.Sprintf("test-metric-int-%d-%d", requestNum, index) +} + +func BenchmarkTraceSizeBytes(b *testing.B) { + td := testdata.GenerateTraceDataManySpansSameResource(8192) + for n := 0; n < b.N; n++ { + fmt.Println(td.Size()) + } +} + +func BenchmarkTraceSizeSpanCount(b *testing.B) { + td := testdata.GenerateTraceDataManySpansSameResource(8192) + for n := 0; n < b.N; n++ { + td.SpanCount() + } +} + +func TestBatchLogProcessor_ReceivingData(t *testing.T) { + // Instantiate the batch processor with low config values to test data + // gets sent through the processor. + cfg := Config{ + Timeout: 200 * time.Millisecond, + SendBatchSize: 50, + } + + requestCount := 100 + logsPerRequest := 5 + sink := new(consumertest.LogsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchLogsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + logDataSlice := make([]pdata.Logs, 0, requestCount) + + for requestNum := 0; requestNum < requestCount; requestNum++ { + ld := testdata.GenerateLogDataManyLogsSameResource(logsPerRequest) + logs := ld.ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs() + for logIndex := 0; logIndex < logsPerRequest; logIndex++ { + logs.At(logIndex).SetName(getTestLogName(requestNum, logIndex)) + } + logDataSlice = append(logDataSlice, ld.Clone()) + assert.NoError(t, batcher.ConsumeLogs(context.Background(), ld)) + } + + // Added to test case with empty resources sent. + ld := testdata.GenerateLogDataEmpty() + assert.NoError(t, batcher.ConsumeLogs(context.Background(), ld)) + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*logsPerRequest, sink.LogRecordsCount()) + receivedMds := sink.AllLogs() + logsReceivedByName := logsReceivedByName(receivedMds) + for requestNum := 0; requestNum < requestCount; requestNum++ { + logs := logDataSlice[requestNum].ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs() + for logIndex := 0; logIndex < logsPerRequest; logIndex++ { + require.EqualValues(t, + logs.At(logIndex), + logsReceivedByName[getTestLogName(requestNum, logIndex)]) + } + } +} + +func TestBatchLogProcessor_BatchSize(t *testing.T) { + views := MetricViews() + require.NoError(t, view.Register(views...)) + defer view.Unregister(views...) + + // Instantiate the batch processor with low config values to test data + // gets sent through the processor. + cfg := Config{ + Timeout: 100 * time.Millisecond, + SendBatchSize: 50, + } + + requestCount := 100 + logsPerRequest := 5 + sink := new(consumertest.LogsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchLogsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + start := time.Now() + size := 0 + for requestNum := 0; requestNum < requestCount; requestNum++ { + ld := testdata.GenerateLogDataManyLogsSameResource(logsPerRequest) + size += ld.SizeBytes() + assert.NoError(t, batcher.ConsumeLogs(context.Background(), ld)) + } + require.NoError(t, batcher.Shutdown(context.Background())) + + elapsed := time.Since(start) + require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds()) + + expectedBatchesNum := requestCount * logsPerRequest / int(cfg.SendBatchSize) + expectedBatchingFactor := int(cfg.SendBatchSize) / logsPerRequest + + require.Equal(t, requestCount*logsPerRequest, sink.LogRecordsCount()) + receivedMds := sink.AllLogs() + require.Equal(t, expectedBatchesNum, len(receivedMds)) + for _, ld := range receivedMds { + require.Equal(t, expectedBatchingFactor, ld.ResourceLogs().Len()) + for i := 0; i < expectedBatchingFactor; i++ { + require.Equal(t, logsPerRequest, ld.ResourceLogs().At(i).InstrumentationLibraryLogs().At(0).Logs().Len()) + } + } + + viewData, err := view.RetrieveData("processor/batch/" + statBatchSendSize.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData := viewData[0].Data.(*view.DistributionData) + assert.Equal(t, int64(expectedBatchesNum), distData.Count) + assert.Equal(t, sink.LogRecordsCount(), int(distData.Sum())) + assert.Equal(t, cfg.SendBatchSize, uint32(distData.Min)) + assert.Equal(t, cfg.SendBatchSize, uint32(distData.Max)) + + viewData, err = view.RetrieveData("processor/batch/" + statBatchSendSizeBytes.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData = viewData[0].Data.(*view.DistributionData) + assert.Equal(t, int64(expectedBatchesNum), distData.Count) + assert.Equal(t, size, int(distData.Sum())) +} + +func TestBatchLogsProcessor_Timeout(t *testing.T) { + cfg := Config{ + Timeout: 100 * time.Millisecond, + SendBatchSize: 100, + } + requestCount := 5 + logsPerRequest := 10 + sink := new(consumertest.LogsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchLogsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + start := time.Now() + for requestNum := 0; requestNum < requestCount; requestNum++ { + ld := testdata.GenerateLogDataManyLogsSameResource(logsPerRequest) + assert.NoError(t, batcher.ConsumeLogs(context.Background(), ld)) + } + + // Wait for at least one batch to be sent. + for { + if sink.LogRecordsCount() != 0 { + break + } + <-time.After(cfg.Timeout) + } + + elapsed := time.Since(start) + require.LessOrEqual(t, cfg.Timeout.Nanoseconds(), elapsed.Nanoseconds()) + + // This should not change the results in the sink, verified by the expectedBatchesNum + require.NoError(t, batcher.Shutdown(context.Background())) + + expectedBatchesNum := 1 + expectedBatchingFactor := 5 + + require.Equal(t, requestCount*logsPerRequest, sink.LogRecordsCount()) + receivedMds := sink.AllLogs() + require.Equal(t, expectedBatchesNum, len(receivedMds)) + for _, ld := range receivedMds { + require.Equal(t, expectedBatchingFactor, ld.ResourceLogs().Len()) + for i := 0; i < expectedBatchingFactor; i++ { + require.Equal(t, logsPerRequest, ld.ResourceLogs().At(i).InstrumentationLibraryLogs().At(0).Logs().Len()) + } + } +} + +func TestBatchLogProcessor_Shutdown(t *testing.T) { + cfg := Config{ + Timeout: 3 * time.Second, + SendBatchSize: 1000, + } + requestCount := 5 + logsPerRequest := 10 + sink := new(consumertest.LogsSink) + + createParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + batcher := newBatchLogsProcessor(createParams, sink, &cfg, configtelemetry.LevelDetailed) + require.NoError(t, batcher.Start(context.Background(), componenttest.NewNopHost())) + + for requestNum := 0; requestNum < requestCount; requestNum++ { + ld := testdata.GenerateLogDataManyLogsSameResource(logsPerRequest) + assert.NoError(t, batcher.ConsumeLogs(context.Background(), ld)) + } + + require.NoError(t, batcher.Shutdown(context.Background())) + + require.Equal(t, requestCount*logsPerRequest, sink.LogRecordsCount()) + require.Equal(t, 1, len(sink.AllLogs())) +} + +func getTestLogName(requestNum, index int) string { + return fmt.Sprintf("test-log-int-%d-%d", requestNum, index) +} + +func logsReceivedByName(lds []pdata.Logs) map[string]pdata.LogRecord { + logsReceivedByName := map[string]pdata.LogRecord{} + for i := range lds { + ld := lds[i] + rms := ld.ResourceLogs() + for i := 0; i < rms.Len(); i++ { + ilms := rms.At(i).InstrumentationLibraryLogs() + for j := 0; j < ilms.Len(); j++ { + logs := ilms.At(j).Logs() + for k := 0; k < logs.Len(); k++ { + log := logs.At(k) + logsReceivedByName[log.Name()] = log + } + } + } + } + return logsReceivedByName +} diff --git a/internal/otel_collector/processor/batchprocessor/config.go b/internal/otel_collector/processor/batchprocessor/config.go new file mode 100644 index 00000000000..ed66435ba15 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/config.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "time" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for batch processor. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + + // Timeout sets the time after which a batch will be sent regardless of size. + Timeout time.Duration `mapstructure:"timeout,omitempty"` + + // SendBatchSize is the size of a batch which after hit, will trigger it to be sent. + SendBatchSize uint32 `mapstructure:"send_batch_size,omitempty"` + + // SendBatchMaxSize is the maximum size of a batch. Larger batches are split into smaller units. + // Default value is 0, that means no maximum size. + SendBatchMaxSize uint32 `mapstructure:"send_batch_max_size,omitempty"` +} diff --git a/internal/otel_collector/processor/batchprocessor/config_test.go b/internal/otel_collector/processor/batchprocessor/config_test.go new file mode 100644 index 00000000000..301a310ef36 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/config_test.go @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + p0 := cfg.Processors["batch"] + assert.Equal(t, p0, factory.CreateDefaultConfig()) + + p1 := cfg.Processors["batch/2"] + + timeout := time.Second * 10 + sendBatchSize := uint32(10000) + sendBatchMaxSize := uint32(11000) + + assert.Equal(t, p1, + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "batch", + NameVal: "batch/2", + }, + SendBatchSize: sendBatchSize, + SendBatchMaxSize: sendBatchMaxSize, + Timeout: timeout, + }) +} diff --git a/internal/otel_collector/processor/batchprocessor/factory.go b/internal/otel_collector/processor/batchprocessor/factory.go new file mode 100644 index 00000000000..9e5d4ca441d --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/factory.go @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "batch" + + defaultSendBatchSize = uint32(8192) + defaultTimeout = 200 * time.Millisecond +) + +// NewFactory returns a new factory for the Batch processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor), + processorhelper.WithMetrics(createMetricsProcessor), + processorhelper.WithLogs(createLogsProcessor)) +} + +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + SendBatchSize: defaultSendBatchSize, + Timeout: defaultTimeout, + } +} + +func createTraceProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, +) (component.TracesProcessor, error) { + oCfg := cfg.(*Config) + level := configtelemetry.GetMetricsLevelFlagValue() + return newBatchTracesProcessor(params, nextConsumer, oCfg, level), nil +} + +func createMetricsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsProcessor, error) { + oCfg := cfg.(*Config) + level := configtelemetry.GetMetricsLevelFlagValue() + return newBatchMetricsProcessor(params, nextConsumer, oCfg, level), nil +} + +func createLogsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.LogsConsumer, +) (component.LogsProcessor, error) { + oCfg := cfg.(*Config) + level := configtelemetry.GetMetricsLevelFlagValue() + return newBatchLogsProcessor(params, nextConsumer, oCfg, level), nil +} diff --git a/internal/otel_collector/processor/batchprocessor/factory_test.go b/internal/otel_collector/processor/batchprocessor/factory_test.go new file mode 100644 index 00000000000..28d36d15d21 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/factory_test.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateProcessor(t *testing.T) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig() + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + tp, err := factory.CreateTracesProcessor(context.Background(), creationParams, cfg, nil) + assert.NotNil(t, tp) + assert.NoError(t, err, "cannot create trace processor") + + mp, err := factory.CreateMetricsProcessor(context.Background(), creationParams, cfg, nil) + assert.NotNil(t, mp) + assert.NoError(t, err, "cannot create metric processor") + + lp, err := factory.CreateLogsProcessor(context.Background(), creationParams, cfg, nil) + assert.NotNil(t, lp) + assert.NoError(t, err, "cannot create logs processor") +} diff --git a/internal/otel_collector/processor/batchprocessor/metrics.go b/internal/otel_collector/processor/batchprocessor/metrics.go new file mode 100644 index 00000000000..6852b334053 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/metrics.go @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/processor" +) + +var ( + statBatchSizeTriggerSend = stats.Int64("batch_size_trigger_send", "Number of times the batch was sent due to a size trigger", stats.UnitDimensionless) + statTimeoutTriggerSend = stats.Int64("timeout_trigger_send", "Number of times the batch was sent due to a timeout trigger", stats.UnitDimensionless) + statBatchSendSize = stats.Int64("batch_send_size", "Number of units in the batch", stats.UnitDimensionless) + statBatchSendSizeBytes = stats.Int64("batch_send_size_bytes", "Number of bytes in batch that was sent", stats.UnitBytes) +) + +// MetricViews returns the metrics views related to batching +func MetricViews() []*view.View { + processorTagKeys := []tag.Key{processor.TagProcessorNameKey} + + countBatchSizeTriggerSendView := &view.View{ + Name: statBatchSizeTriggerSend.Name(), + Measure: statBatchSizeTriggerSend, + Description: statBatchSizeTriggerSend.Description(), + TagKeys: processorTagKeys, + Aggregation: view.Sum(), + } + + countTimeoutTriggerSendView := &view.View{ + Name: statTimeoutTriggerSend.Name(), + Measure: statTimeoutTriggerSend, + Description: statTimeoutTriggerSend.Description(), + TagKeys: processorTagKeys, + Aggregation: view.Sum(), + } + + distributionBatchSendSizeView := &view.View{ + Name: statBatchSendSize.Name(), + Measure: statBatchSendSize, + Description: statBatchSendSize.Description(), + TagKeys: processorTagKeys, + Aggregation: view.Distribution(10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000), + } + + distributionBatchSendSizeBytesView := &view.View{ + Name: statBatchSendSizeBytes.Name(), + Measure: statBatchSendSizeBytes, + Description: statBatchSendSizeBytes.Description(), + TagKeys: processorTagKeys, + Aggregation: view.Distribution(10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, + 100_000, 200_000, 300_000, 400_000, 500_000, 600_000, 700_000, 800_00, 900_000, + 1000_000, 2000_000, 3000_000, 4000_000, 5000_000, 6000_000, 7000_000, 8000_000, 9000_000), + } + + legacyViews := []*view.View{ + countBatchSizeTriggerSendView, + countTimeoutTriggerSendView, + distributionBatchSendSizeView, + distributionBatchSendSizeBytesView, + } + + return obsreport.ProcessorMetricViews(typeStr, legacyViews) +} diff --git a/internal/otel_collector/processor/batchprocessor/metrics_test.go b/internal/otel_collector/processor/batchprocessor/metrics_test.go new file mode 100644 index 00000000000..167186e37b1 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/metrics_test.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBatchProcessorMetrics(t *testing.T) { + viewNames := []string{ + "batch_size_trigger_send", + "timeout_trigger_send", + "batch_send_size", + "batch_send_size_bytes", + } + views := MetricViews() + for i, viewName := range viewNames { + assert.Equal(t, "processor/batch/"+viewName, views[i].Name) + } +} diff --git a/internal/otel_collector/processor/batchprocessor/splittraces.go b/internal/otel_collector/processor/batchprocessor/splittraces.go new file mode 100644 index 00000000000..34c0c179f0d --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/splittraces.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// splitTrace removes spans from the input trace and returns a new trace of the specified size. +func splitTrace(size int, toSplit pdata.Traces) pdata.Traces { + if toSplit.SpanCount() <= size { + return toSplit + } + copiedSpans := 0 + result := pdata.NewTraces() + rss := toSplit.ResourceSpans() + for i := rss.Len() - 1; i >= 0; i-- { + rs := rss.At(i) + destRs := pdata.NewResourceSpans() + rs.Resource().CopyTo(destRs.Resource()) + result.ResourceSpans().Append(destRs) + + for j := rs.InstrumentationLibrarySpans().Len() - 1; j >= 0; j-- { + instSpans := rs.InstrumentationLibrarySpans().At(j) + destInstSpans := pdata.NewInstrumentationLibrarySpans() + destRs.InstrumentationLibrarySpans().Append(destInstSpans) + instSpans.InstrumentationLibrary().CopyTo(destInstSpans.InstrumentationLibrary()) + + if size-copiedSpans >= instSpans.Spans().Len() { + destInstSpans.Spans().Resize(instSpans.Spans().Len()) + } else { + destInstSpans.Spans().Resize(size - copiedSpans) + } + for k, destIdx := instSpans.Spans().Len()-1, 0; k >= 0 && copiedSpans < size; k, destIdx = k-1, destIdx+1 { + span := instSpans.Spans().At(k) + span.CopyTo(destInstSpans.Spans().At(destIdx)) + copiedSpans++ + // remove span + instSpans.Spans().Resize(instSpans.Spans().Len() - 1) + } + if instSpans.Spans().Len() == 0 { + rs.InstrumentationLibrarySpans().Resize(rs.InstrumentationLibrarySpans().Len() - 1) + } + if copiedSpans == size { + return result + } + } + if rs.InstrumentationLibrarySpans().Len() == 0 { + rss.Resize(rss.Len() - 1) + } + } + return result +} diff --git a/internal/otel_collector/processor/batchprocessor/splittraces_test.go b/internal/otel_collector/processor/batchprocessor/splittraces_test.go new file mode 100644 index 00000000000..6e54cc15f51 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/splittraces_test.go @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package batchprocessor + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestSplitTraces_noop(t *testing.T) { + td := testdata.GenerateTraceDataManySpansSameResource(20) + splitSize := 40 + split := splitTrace(splitSize, td) + assert.Equal(t, td, split) + + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(5) + assert.EqualValues(t, td, split) +} + +func TestSplitTraces(t *testing.T) { + td := testdata.GenerateTraceDataManySpansSameResource(20) + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + for i := 0; i < spans.Len(); i++ { + spans.At(i).SetName(getTestSpanName(0, i)) + } + cp := pdata.NewTraces() + cp.ResourceSpans().Resize(1) + cp.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + cp.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(5) + cpSpans := cp.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + td.ResourceSpans().At(0).Resource().CopyTo( + cp.ResourceSpans().At(0).Resource()) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).InstrumentationLibrary().CopyTo( + cp.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).InstrumentationLibrary()) + spans.At(19).CopyTo(cpSpans.At(0)) + spans.At(18).CopyTo(cpSpans.At(1)) + spans.At(17).CopyTo(cpSpans.At(2)) + spans.At(16).CopyTo(cpSpans.At(3)) + spans.At(15).CopyTo(cpSpans.At(4)) + + splitSize := 5 + split := splitTrace(splitSize, td) + assert.Equal(t, splitSize, split.SpanCount()) + assert.Equal(t, cp, split) + assert.Equal(t, 15, td.SpanCount()) + assert.Equal(t, "test-span-0-19", split.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).Name()) + assert.Equal(t, "test-span-0-15", split.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(4).Name()) +} + +func TestSplitTracesMultipleResourceSpans(t *testing.T) { + td := testdata.GenerateTraceDataManySpansSameResource(20) + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + for i := 0; i < spans.Len(); i++ { + spans.At(i).SetName(getTestSpanName(0, i)) + } + td.ResourceSpans().Resize(2) + // add second index to resource spans + testdata.GenerateTraceDataManySpansSameResource(20). + ResourceSpans().At(0).CopyTo(td.ResourceSpans().At(1)) + spans = td.ResourceSpans().At(1).InstrumentationLibrarySpans().At(0).Spans() + for i := 0; i < spans.Len(); i++ { + spans.At(i).SetName(getTestSpanName(1, i)) + } + + splitSize := 5 + split := splitTrace(splitSize, td) + assert.Equal(t, splitSize, split.SpanCount()) + assert.Equal(t, 35, td.SpanCount()) + assert.Equal(t, "test-span-1-19", split.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).Name()) + assert.Equal(t, "test-span-1-15", split.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(4).Name()) +} + +func TestSplitTracesMultipleResourceSpans_split_size_greater_than_span_size(t *testing.T) { + td := testdata.GenerateTraceDataManySpansSameResource(20) + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + for i := 0; i < spans.Len(); i++ { + spans.At(i).SetName(getTestSpanName(0, i)) + } + td.ResourceSpans().Resize(2) + // add second index to resource spans + testdata.GenerateTraceDataManySpansSameResource(20). + ResourceSpans().At(0).CopyTo(td.ResourceSpans().At(1)) + spans = td.ResourceSpans().At(1).InstrumentationLibrarySpans().At(0).Spans() + for i := 0; i < spans.Len(); i++ { + spans.At(i).SetName(getTestSpanName(1, i)) + } + + splitSize := 25 + split := splitTrace(splitSize, td) + assert.Equal(t, splitSize, split.SpanCount()) + assert.Equal(t, 40-splitSize, td.SpanCount()) + assert.Equal(t, 1, td.ResourceSpans().Len()) + assert.Equal(t, "test-span-1-19", split.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).Name()) + assert.Equal(t, "test-span-1-0", split.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(19).Name()) + assert.Equal(t, "test-span-0-19", split.ResourceSpans().At(1).InstrumentationLibrarySpans().At(0).Spans().At(0).Name()) + assert.Equal(t, "test-span-0-15", split.ResourceSpans().At(1).InstrumentationLibrarySpans().At(0).Spans().At(4).Name()) +} diff --git a/internal/otel_collector/processor/batchprocessor/testdata/config.yaml b/internal/otel_collector/processor/batchprocessor/testdata/config.yaml new file mode 100644 index 00000000000..7cfcda24075 --- /dev/null +++ b/internal/otel_collector/processor/batchprocessor/testdata/config.yaml @@ -0,0 +1,19 @@ +receivers: + examplereceiver: + +processors: + batch: + batch/2: + timeout: 10s + send_batch_size: 10000 + send_batch_max_size: 11000 + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [batch/2] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/cloningfanoutconnector.go b/internal/otel_collector/processor/cloningfanoutconnector.go new file mode 100644 index 00000000000..a4715de7633 --- /dev/null +++ b/internal/otel_collector/processor/cloningfanoutconnector.go @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processor + +import ( + "context" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// This file contains implementations of cloning Trace/Metrics connectors +// that fan out the data to multiple other consumers. Cloning connectors create +// clones of data before fanning out, which ensures each consumer gets their +// own copy of data and is free to modify it. + +// NewMetricsCloningFanOutConnector wraps multiple metrics consumers in a single one and clones the data +// before fanning out. +func NewMetricsCloningFanOutConnector(mcs []consumer.MetricsConsumer) consumer.MetricsConsumer { + if len(mcs) == 1 { + // Don't wrap if no need to do it. + return mcs[0] + } + return metricsCloningFanOutConnector(mcs) +} + +type metricsCloningFanOutConnector []consumer.MetricsConsumer + +var _ consumer.MetricsConsumer = (*metricsCloningFanOutConnector)(nil) + +// ConsumeMetrics exports the MetricsData to all consumers wrapped by the current one. +func (mfc metricsCloningFanOutConnector) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + var errs []error + + // Fan out to first len-1 consumers. + for i := 0; i < len(mfc)-1; i++ { + // Create a clone of data. We need to clone because consumers may modify the data. + if err := mfc[i].ConsumeMetrics(ctx, md.Clone()); err != nil { + errs = append(errs, err) + } + } + + if len(mfc) > 0 { + // Give the original data to the last consumer. + lastTc := mfc[len(mfc)-1] + if err := lastTc.ConsumeMetrics(ctx, md); err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} + +// NewTracesCloningFanOutConnector wraps multiple traces consumers in a single one and clones the data +// before fanning out. +func NewTracesCloningFanOutConnector(tcs []consumer.TracesConsumer) consumer.TracesConsumer { + if len(tcs) == 1 { + // Don't wrap if no need to do it. + return tcs[0] + } + return tracesCloningFanOutConnector(tcs) +} + +type tracesCloningFanOutConnector []consumer.TracesConsumer + +var _ consumer.TracesConsumer = (*tracesCloningFanOutConnector)(nil) + +// ConsumeTraceData exports the span data to all trace consumers wrapped by the current one. +func (tfc tracesCloningFanOutConnector) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + var errs []error + + // Fan out to first len-1 consumers. + for i := 0; i < len(tfc)-1; i++ { + // Create a clone of data. We need to clone because consumers may modify the data. + if err := tfc[i].ConsumeTraces(ctx, td.Clone()); err != nil { + errs = append(errs, err) + } + } + + if len(tfc) > 0 { + // Give the original data to the last consumer. + lastTc := tfc[len(tfc)-1] + if err := lastTc.ConsumeTraces(ctx, td); err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} + +// NewLogsCloningFanOutConnector wraps multiple trace consumers in a single one. +func NewLogsCloningFanOutConnector(lcs []consumer.LogsConsumer) consumer.LogsConsumer { + if len(lcs) == 1 { + // Don't wrap if no need to do it. + return lcs[0] + } + return logsCloningFanOutConnector(lcs) +} + +type logsCloningFanOutConnector []consumer.LogsConsumer + +var _ consumer.LogsConsumer = (*logsCloningFanOutConnector)(nil) + +// ConsumeLogs exports the log data to all consumers wrapped by the current one. +func (lfc logsCloningFanOutConnector) ConsumeLogs(ctx context.Context, ld pdata.Logs) error { + var errs []error + + // Fan out to first len-1 consumers. + for i := 0; i < len(lfc)-1; i++ { + // Create a clone of data. We need to clone because consumers may modify the data. + if err := lfc[i].ConsumeLogs(ctx, ld.Clone()); err != nil { + errs = append(errs, err) + } + } + + if len(lfc) > 0 { + // Give the original data to the last consumer. + lastTc := lfc[len(lfc)-1] + if err := lastTc.ConsumeLogs(ctx, ld); err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} diff --git a/internal/otel_collector/processor/cloningfanoutconnector_test.go b/internal/otel_collector/processor/cloningfanoutconnector_test.go new file mode 100644 index 00000000000..39fdf043a0e --- /dev/null +++ b/internal/otel_collector/processor/cloningfanoutconnector_test.go @@ -0,0 +1,155 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestTraceProcessorCloningNotMultiplexing(t *testing.T) { + nop := consumertest.NewTracesNop() + tfc := NewTracesCloningFanOutConnector([]consumer.TracesConsumer{nop}) + assert.Same(t, nop, tfc) +} + +func TestTraceProcessorCloningMultiplexing(t *testing.T) { + processors := make([]consumer.TracesConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.TracesSink) + } + + tfc := NewTracesCloningFanOutConnector(processors) + td := testdata.GenerateTraceDataTwoSpansSameResource() + + var wantSpansCount = 0 + for i := 0; i < 2; i++ { + wantSpansCount += td.SpanCount() + err := tfc.ConsumeTraces(context.Background(), td) + if err != nil { + t.Errorf("Wanted nil got error") + return + } + } + + for i, p := range processors { + m := p.(*consumertest.TracesSink) + assert.Equal(t, wantSpansCount, m.SpansCount()) + spanOrig := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + allTraces := m.AllTraces() + spanClone := allTraces[0].ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + if i < len(processors)-1 { + assert.True(t, td.ResourceSpans().At(0).Resource() != allTraces[0].ResourceSpans().At(0).Resource()) + assert.True(t, spanOrig != spanClone) + } else { + assert.True(t, td.ResourceSpans().At(0).Resource() == allTraces[0].ResourceSpans().At(0).Resource()) + assert.True(t, spanOrig == spanClone) + } + assert.EqualValues(t, td.ResourceSpans().At(0).Resource(), allTraces[0].ResourceSpans().At(0).Resource()) + assert.EqualValues(t, spanOrig, spanClone) + } +} + +func TestMetricsProcessorCloningNotMultiplexing(t *testing.T) { + nop := consumertest.NewMetricsNop() + mfc := NewMetricsFanOutConnector([]consumer.MetricsConsumer{nop}) + assert.Same(t, nop, mfc) +} + +func TestMetricsProcessorCloningMultiplexing(t *testing.T) { + processors := make([]consumer.MetricsConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.MetricsSink) + } + + mfc := NewMetricsCloningFanOutConnector(processors) + md := testdata.GeneratMetricsAllTypesWithSampleDatapoints() + + var wantMetricsCount = 0 + for i := 0; i < 2; i++ { + wantMetricsCount += md.MetricCount() + err := mfc.ConsumeMetrics(context.Background(), md) + if err != nil { + t.Errorf("Wanted nil got error") + return + } + } + + for i, p := range processors { + m := p.(*consumertest.MetricsSink) + assert.Equal(t, wantMetricsCount, m.MetricsCount()) + metricOrig := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + allMetrics := m.AllMetrics() + metricClone := allMetrics[0].ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + if i < len(processors)-1 { + assert.True(t, md.ResourceMetrics().At(0).Resource() != allMetrics[0].ResourceMetrics().At(0).Resource()) + assert.True(t, metricOrig != metricClone) + } else { + assert.True(t, md.ResourceMetrics().At(0).Resource() == allMetrics[0].ResourceMetrics().At(0).Resource()) + assert.True(t, metricOrig == metricClone) + } + assert.EqualValues(t, md.ResourceMetrics().At(0).Resource(), allMetrics[0].ResourceMetrics().At(0).Resource()) + assert.EqualValues(t, metricOrig, metricClone) + } +} + +func TestLogsProcessorCloningNotMultiplexing(t *testing.T) { + nop := consumertest.NewLogsNop() + lfc := NewLogsCloningFanOutConnector([]consumer.LogsConsumer{nop}) + assert.Same(t, nop, lfc) +} + +func TestLogsProcessorCloningMultiplexing(t *testing.T) { + processors := make([]consumer.LogsConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.LogsSink) + } + + mfc := NewLogsCloningFanOutConnector(processors) + ld := testdata.GenerateLogDataOneLog() + + var wantMetricsCount = 0 + for i := 0; i < 2; i++ { + wantMetricsCount += ld.LogRecordCount() + err := mfc.ConsumeLogs(context.Background(), ld) + if err != nil { + t.Errorf("Wanted nil got error") + return + } + } + + for i, p := range processors { + m := p.(*consumertest.LogsSink) + assert.Equal(t, wantMetricsCount, m.LogRecordsCount()) + metricOrig := ld.ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().At(0) + allLogs := m.AllLogs() + metricClone := allLogs[0].ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().At(0) + if i < len(processors)-1 { + assert.True(t, ld.ResourceLogs().At(0).Resource() != allLogs[0].ResourceLogs().At(0).Resource()) + assert.True(t, metricOrig != metricClone) + } else { + assert.True(t, ld.ResourceLogs().At(0).Resource() == allLogs[0].ResourceLogs().At(0).Resource()) + assert.True(t, metricOrig == metricClone) + } + assert.EqualValues(t, ld.ResourceLogs().At(0).Resource(), allLogs[0].ResourceLogs().At(0).Resource()) + assert.EqualValues(t, metricOrig, metricClone) + } +} diff --git a/internal/otel_collector/processor/fanoutconnector.go b/internal/otel_collector/processor/fanoutconnector.go new file mode 100644 index 00000000000..f590c4280b0 --- /dev/null +++ b/internal/otel_collector/processor/fanoutconnector.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processor + +import ( + "context" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// This file contains implementations of Trace/Metrics connectors +// that fan out the data to multiple other consumers. + +// NewMetricsFanOutConnector wraps multiple metrics consumers in a single one. +func NewMetricsFanOutConnector(mcs []consumer.MetricsConsumer) consumer.MetricsConsumer { + if len(mcs) == 1 { + // Don't wrap if no need to do it. + return mcs[0] + } + return metricsFanOutConnector(mcs) +} + +type metricsFanOutConnector []consumer.MetricsConsumer + +var _ consumer.MetricsConsumer = (*metricsFanOutConnector)(nil) + +// ConsumeMetricsData exports the MetricsData to all consumers wrapped by the current one. +func (mfc metricsFanOutConnector) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + var errs []error + for _, mc := range mfc { + if err := mc.ConsumeMetrics(ctx, md); err != nil { + errs = append(errs, err) + } + } + return componenterror.CombineErrors(errs) +} + +// NewTracesFanOutConnector wraps multiple trace consumers in a single one. +func NewTracesFanOutConnector(tcs []consumer.TracesConsumer) consumer.TracesConsumer { + if len(tcs) == 1 { + // Don't wrap if no need to do it. + return tcs[0] + } + return traceFanOutConnector(tcs) +} + +type traceFanOutConnector []consumer.TracesConsumer + +var _ consumer.TracesConsumer = (*traceFanOutConnector)(nil) + +// ConsumeTraces exports the span data to all trace consumers wrapped by the current one. +func (tfc traceFanOutConnector) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + var errs []error + for _, tc := range tfc { + if err := tc.ConsumeTraces(ctx, td); err != nil { + errs = append(errs, err) + } + } + return componenterror.CombineErrors(errs) +} + +// NewLogsFanOutConnector wraps multiple log consumers in a single one. +func NewLogsFanOutConnector(lcs []consumer.LogsConsumer) consumer.LogsConsumer { + if len(lcs) == 1 { + // Don't wrap if no need to do it. + return lcs[0] + } + return logsFanOutConnector(lcs) +} + +type logsFanOutConnector []consumer.LogsConsumer + +var _ consumer.LogsConsumer = (*logsFanOutConnector)(nil) + +// Consume exports the log data to all consumers wrapped by the current one. +func (fc logsFanOutConnector) ConsumeLogs(ctx context.Context, ld pdata.Logs) error { + var errs []error + for _, tc := range fc { + if err := tc.ConsumeLogs(ctx, ld); err != nil { + errs = append(errs, err) + } + } + return componenterror.CombineErrors(errs) +} diff --git a/internal/otel_collector/processor/fanoutconnector_test.go b/internal/otel_collector/processor/fanoutconnector_test.go new file mode 100644 index 00000000000..69fb3f21511 --- /dev/null +++ b/internal/otel_collector/processor/fanoutconnector_test.go @@ -0,0 +1,204 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processor + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestTracesProcessorNotMultiplexing(t *testing.T) { + nop := consumertest.NewTracesNop() + tfc := NewTracesFanOutConnector([]consumer.TracesConsumer{nop}) + assert.Same(t, nop, tfc) +} + +func TestTracesProcessorMultiplexing(t *testing.T) { + processors := make([]consumer.TracesConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.TracesSink) + } + + tfc := NewTracesFanOutConnector(processors) + td := testdata.GenerateTraceDataOneSpan() + + var wantSpansCount = 0 + for i := 0; i < 2; i++ { + wantSpansCount += td.SpanCount() + err := tfc.ConsumeTraces(context.Background(), td) + if err != nil { + t.Errorf("Wanted nil got error") + return + } + } + + for _, p := range processors { + m := p.(*consumertest.TracesSink) + assert.Equal(t, wantSpansCount, m.SpansCount()) + assert.EqualValues(t, td, m.AllTraces()[0]) + } +} + +func TestTraceProcessorWhenOneErrors(t *testing.T) { + processors := make([]consumer.TracesConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.TracesSink) + } + + // Make one processor return error + processors[1].(*consumertest.TracesSink).SetConsumeError(errors.New("my_error")) + + tfc := NewTracesFanOutConnector(processors) + td := testdata.GenerateTraceDataOneSpan() + + var wantSpansCount = 0 + for i := 0; i < 2; i++ { + wantSpansCount += td.SpanCount() + err := tfc.ConsumeTraces(context.Background(), td) + if err == nil { + t.Errorf("Wanted error got nil") + return + } + } + + assert.Equal(t, 0, processors[1].(*consumertest.TracesSink).SpansCount()) + assert.Equal(t, wantSpansCount, processors[0].(*consumertest.TracesSink).SpansCount()) + assert.Equal(t, wantSpansCount, processors[2].(*consumertest.TracesSink).SpansCount()) +} + +func TestMetricsProcessorNotMultiplexing(t *testing.T) { + nop := consumertest.NewMetricsNop() + mfc := NewMetricsFanOutConnector([]consumer.MetricsConsumer{nop}) + assert.Same(t, nop, mfc) +} + +func TestMetricsProcessorMultiplexing(t *testing.T) { + processors := make([]consumer.MetricsConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.MetricsSink) + } + + mfc := NewMetricsFanOutConnector(processors) + md := testdata.GenerateMetricsOneMetric() + + var wantMetricsCount = 0 + for i := 0; i < 2; i++ { + wantMetricsCount += md.MetricCount() + err := mfc.ConsumeMetrics(context.Background(), md) + if err != nil { + t.Errorf("Wanted nil got error") + return + } + } + + for _, p := range processors { + m := p.(*consumertest.MetricsSink) + assert.Equal(t, wantMetricsCount, m.MetricsCount()) + assert.EqualValues(t, md, m.AllMetrics()[0]) + } +} + +func TestMetricsProcessorWhenOneErrors(t *testing.T) { + processors := make([]consumer.MetricsConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.MetricsSink) + } + + // Make one processor return error + processors[1].(*consumertest.MetricsSink).SetConsumeError(errors.New("my_error")) + + mfc := NewMetricsFanOutConnector(processors) + md := testdata.GenerateMetricsOneMetric() + + var wantMetricsCount = 0 + for i := 0; i < 2; i++ { + wantMetricsCount += md.MetricCount() + err := mfc.ConsumeMetrics(context.Background(), md) + if err == nil { + t.Errorf("Wanted error got nil") + return + } + } + + assert.Equal(t, 0, processors[1].(*consumertest.MetricsSink).MetricsCount()) + assert.Equal(t, wantMetricsCount, processors[0].(*consumertest.MetricsSink).MetricsCount()) + assert.Equal(t, wantMetricsCount, processors[2].(*consumertest.MetricsSink).MetricsCount()) +} + +func TestLogsProcessorNotMultiplexing(t *testing.T) { + nop := consumertest.NewLogsNop() + lfc := NewLogsFanOutConnector([]consumer.LogsConsumer{nop}) + assert.Same(t, nop, lfc) +} + +func TestLogsProcessorMultiplexing(t *testing.T) { + processors := make([]consumer.LogsConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.LogsSink) + } + + lfc := NewLogsFanOutConnector(processors) + ld := testdata.GenerateLogDataOneLog() + + var wantMetricsCount = 0 + for i := 0; i < 2; i++ { + wantMetricsCount += ld.LogRecordCount() + err := lfc.ConsumeLogs(context.Background(), ld) + if err != nil { + t.Errorf("Wanted nil got error") + return + } + } + + for _, p := range processors { + m := p.(*consumertest.LogsSink) + assert.Equal(t, wantMetricsCount, m.LogRecordsCount()) + assert.EqualValues(t, ld, m.AllLogs()[0]) + } +} + +func TestLogsProcessorWhenOneErrors(t *testing.T) { + processors := make([]consumer.LogsConsumer, 3) + for i := range processors { + processors[i] = new(consumertest.LogsSink) + } + + // Make one processor return error + processors[1].(*consumertest.LogsSink).SetConsumeError(errors.New("my_error")) + + lfc := NewLogsFanOutConnector(processors) + ld := testdata.GenerateLogDataOneLog() + + var wantMetricsCount = 0 + for i := 0; i < 2; i++ { + wantMetricsCount += ld.LogRecordCount() + err := lfc.ConsumeLogs(context.Background(), ld) + if err == nil { + t.Errorf("Wanted error got nil") + return + } + } + + assert.Equal(t, 0, processors[1].(*consumertest.LogsSink).LogRecordsCount()) + assert.Equal(t, wantMetricsCount, processors[0].(*consumertest.LogsSink).LogRecordsCount()) + assert.Equal(t, wantMetricsCount, processors[2].(*consumertest.LogsSink).LogRecordsCount()) +} diff --git a/internal/otel_collector/processor/filterprocessor/README.md b/internal/otel_collector/processor/filterprocessor/README.md new file mode 100644 index 00000000000..98dd6a825cd --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/README.md @@ -0,0 +1,107 @@ +# Filter Processor + +Supported pipeline types: metrics + +The filter processor can be configured to include or exclude metrics based on +metric name in the case of the 'strict' or 'regexp' match types, or based on other +metric attributes in the case of the 'expr' match type. Please refer to +[config.go](./config.go) for the config spec. + +It takes a pipeline type, of which only `metrics` is supported, followed by an +action: +- `include`: Any names NOT matching filters are excluded from remainder of pipeline +- `exclude`: Any names matching filters are excluded from remainder of pipeline + +For the actions the following parameters are required: + - `match_type`: strict|regexp|expr + - `metric_names`: (only for a `match_type` of 'strict' or 'regexp') list of strings or re2 regex patterns + - `expressions`: (only for a `match_type` of 'expr') list of expr expressions (see "Using an 'expr' match_type" below) + +More details can found at [include/exclude metrics](../README.md#includeexclude-metrics). + +Examples: + +```yaml +processors: + filter/1: + metrics: + include: + match_type: regexp + metric_names: + - prefix/.* + - prefix_.* + exclude: + match_type: strict + metric_names: + - hello_world + - hello/world +``` + +Refer to the config files in [testdata](./testdata) for detailed +examples on using the processor. + +### Using an 'expr' match_type + +In addition to matching metric names with the 'strict' or 'regexp' match types, the filter processor +supports matching entire `Metric`s using the [expr](https://github.com/antonmedv/expr) expression engine. + +The 'expr' filter evaluates the supplied boolean expressions _per datapoint_ on a metric, and returns a result +for the entire metric. If any datapoint evaluates to true then the entire metric evaluates to true, otherwise +false. + +Made available to the expression environment are the following: + +* `MetricName` + a variable containing the current Metric's name +* `Label(name)` + a function that takes a label name string as an argument and returns a string: the value of a label with that + name if one exists, or "" +* `HasLabel(name)` + a function that takes a label name string as an argument and returns a boolean: true if the datapoint has a label + with that name, false otherwise + +Example: + +```yaml +processors: + filter/1: + metrics: + exclude: + match_type: expr + expressions: + - MetricName == "my.metric" && Label("my_label") == "abc123" +``` + +The above config will filter out any Metric that both has the name "my.metric" and has at least one datapoint +with a label of 'my_label="abc123"'. + +##### Support for multiple expressions + +As with "strict" and "regexp", multiple "expr" `expressions` are allowed. + +For example, the following two filters have the same effect: they filter out metrics named "system.cpu.time" and +"system.disk.io". + +``` +processors: + filter/expr: + metrics: + exclude: + match_type: expr + expressions: + - MetricName == "system.cpu.time" + - MetricName == "system.disk.io" + filter/strict: + metrics: + exclude: + match_type: strict + metric_names: + - system.cpu.time + - system.disk.io +``` + +The expressions are effectively ORed per datapoint. So for the above 'expr' configuration, given a datapoint, if its +parent Metric's name is "system.cpu.time" or "system.disk.io" then there's a match. The conditions are tested against +all the datapoints in a Metric until there's a match, in which case the entire Metric is considered a match, and in +the above example the Metric will be excluded. If after testing all the datapoints in a Metric against all the +expressions there isn't a match, the entire Metric is considered to be not matching. diff --git a/internal/otel_collector/processor/filterprocessor/config.go b/internal/otel_collector/processor/filterprocessor/config.go new file mode 100644 index 00000000000..f434f2295a7 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/config.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/internal/processor/filtermetric" +) + +// Config defines configuration for Resource processor. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + Metrics MetricFilters `mapstructure:"metrics"` +} + +// MetricFilter filters by Metric properties. +type MetricFilters struct { + // Include match properties describe metrics that should be included in the Collector Service pipeline, + // all other metrics should be dropped from further processing. + // If both Include and Exclude are specified, Include filtering occurs first. + Include *filtermetric.MatchProperties `mapstructure:"include"` + + // Exclude match properties describe metrics that should be excluded from the Collector Service pipeline, + // all other metrics should be included. + // If both Include and Exclude are specified, Include filtering occurs first. + Exclude *filtermetric.MatchProperties `mapstructure:"exclude"` +} diff --git a/internal/otel_collector/processor/filterprocessor/config_test.go b/internal/otel_collector/processor/filterprocessor/config_test.go new file mode 100644 index 00000000000..faf8eafa1f3 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/config_test.go @@ -0,0 +1,313 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/internal/processor/filtermetric" + fsregexp "go.opentelemetry.io/collector/internal/processor/filterset/regexp" +) + +// TestLoadingConfigRegexp tests loading testdata/config_strict.yaml +func TestLoadingConfigStrict(t *testing.T) { + // list of filters used repeatedly on testdata/config_strict.yaml + testDataFilters := []string{ + "hello_world", + "hello/world", + } + + testDataMetricProperties := &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + MetricNames: testDataFilters, + } + + factories, err := componenttest.ExampleComponents() + assert.Nil(t, err) + + factory := NewFactory() + factories.Processors[configmodels.Type(typeStr)] = factory + config, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config_strict.yaml"), factories) + + assert.Nil(t, err) + require.NotNil(t, config) + + tests := []struct { + filterName string + expCfg *Config + }{ + { + filterName: "filter/empty", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/empty", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + }, + }, + }, + }, { + filterName: "filter/include", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/include", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: testDataMetricProperties, + }, + }, + }, { + filterName: "filter/exclude", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/exclude", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Exclude: testDataMetricProperties, + }, + }, + }, { + filterName: "filter/includeexclude", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/includeexclude", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: testDataMetricProperties, + Exclude: &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + MetricNames: []string{"hello_world"}, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.filterName, func(t *testing.T) { + cfg := config.Processors[test.filterName] + assert.Equal(t, test.expCfg, cfg) + }) + } +} + +// TestLoadingConfigRegexp tests loading testdata/config_regexp.yaml +func TestLoadingConfigRegexp(t *testing.T) { + // list of filters used repeatedly on testdata/config.yaml + testDataFilters := []string{ + "prefix/.*", + "prefix_.*", + ".*/suffix", + ".*_suffix", + ".*/contains/.*", + ".*_contains_.*", + "full/name/match", + "full_name_match", + } + + testDataMetricProperties := &filtermetric.MatchProperties{ + MatchType: filtermetric.Regexp, + MetricNames: testDataFilters, + } + + factories, err := componenttest.ExampleComponents() + assert.Nil(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + config, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config_regexp.yaml"), factories) + + assert.Nil(t, err) + require.NotNil(t, config) + + tests := []struct { + filterName string + expCfg *Config + }{ + { + filterName: "filter/include", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/include", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: testDataMetricProperties, + }, + }, + }, { + filterName: "filter/exclude", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/exclude", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Exclude: testDataMetricProperties, + }, + }, + }, { + filterName: "filter/unlimitedcache", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/unlimitedcache", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: &filtermetric.MatchProperties{ + MatchType: filtermetric.Regexp, + RegexpConfig: &fsregexp.Config{ + CacheEnabled: true, + }, + MetricNames: testDataFilters, + }, + }, + }, + }, { + filterName: "filter/limitedcache", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/limitedcache", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Exclude: &filtermetric.MatchProperties{ + MatchType: filtermetric.Regexp, + RegexpConfig: &fsregexp.Config{ + CacheEnabled: true, + CacheMaxNumEntries: 10, + }, + MetricNames: testDataFilters, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.filterName, func(t *testing.T) { + cfg := config.Processors[test.filterName] + assert.Equal(t, test.expCfg, cfg) + }) + } +} + +func TestLoadingConfigExpr(t *testing.T) { + factories, err := componenttest.ExampleComponents() + require.NoError(t, err) + factory := NewFactory() + factories.Processors[configmodels.Type(typeStr)] = factory + config, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config_expr.yaml"), factories) + require.NoError(t, err) + require.NotNil(t, config) + + tests := []struct { + filterName string + expCfg configmodels.Processor + }{ + { + filterName: "filter/empty", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/empty", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: &filtermetric.MatchProperties{ + MatchType: filtermetric.Expr, + }, + }, + }, + }, + { + filterName: "filter/include", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/include", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: &filtermetric.MatchProperties{ + MatchType: filtermetric.Expr, + Expressions: []string{ + `Label("foo") == "bar"`, + `HasLabel("baz")`, + }, + }, + }, + }, + }, + { + filterName: "filter/exclude", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/exclude", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Exclude: &filtermetric.MatchProperties{ + MatchType: filtermetric.Expr, + Expressions: []string{ + `Label("foo") == "bar"`, + `HasLabel("baz")`, + }, + }, + }, + }, + }, + { + filterName: "filter/includeexclude", + expCfg: &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: "filter/includeexclude", + TypeVal: typeStr, + }, + Metrics: MetricFilters{ + Include: &filtermetric.MatchProperties{ + MatchType: filtermetric.Expr, + Expressions: []string{ + `HasLabel("foo")`, + }, + }, + Exclude: &filtermetric.MatchProperties{ + MatchType: filtermetric.Expr, + Expressions: []string{ + `HasLabel("bar")`, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.filterName, func(t *testing.T) { + cfg := config.Processors[test.filterName] + assert.Equal(t, test.expCfg, cfg) + }) + } +} diff --git a/internal/otel_collector/processor/filterprocessor/doc.go b/internal/otel_collector/processor/filterprocessor/doc.go new file mode 100644 index 00000000000..8c0dc18f116 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package filterprocessor implements a processor for filtering +// (dropping) metrics and/or spans by various properties. +package filterprocessor diff --git a/internal/otel_collector/processor/filterprocessor/expr_test.go b/internal/otel_collector/processor/filterprocessor/expr_test.go new file mode 100644 index 00000000000..2f7f99c6092 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/expr_test.go @@ -0,0 +1,207 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/internal/processor/filtermetric" +) + +const filteredMetric = "p0_metric_1" +const filteredLblKey = "pt-label-key-1" +const filteredLblVal = "pt-label-val-1" + +func TestExprError(t *testing.T) { + for mdType := pdata.MetricDataTypeIntGauge; mdType <= pdata.MetricDataTypeDoubleHistogram; mdType++ { + testMatchError(t, mdType) + } +} + +func testMatchError(t *testing.T, mdType pdata.MetricDataType) { + // the "foo" expr expression will cause expr Run() to return an error + proc, next, logs := testProcessor(t, nil, []string{"foo"}) + pdm := testData("", 1, mdType) + err := proc.ConsumeMetrics(context.Background(), pdm) + assert.NoError(t, err) + // assert that metrics not be filtered as a result + assert.Equal(t, []pdata.Metrics{pdm}, next.AllMetrics()) + assert.Equal(t, 1, logs.Len()) + assert.Equal(t, "shouldKeepMetric failed", logs.All()[0].Message) +} + +func TestExprProcessor(t *testing.T) { + testFilter(t, pdata.MetricDataTypeIntGauge) + testFilter(t, pdata.MetricDataTypeDoubleGauge) + testFilter(t, pdata.MetricDataTypeIntSum) + testFilter(t, pdata.MetricDataTypeDoubleSum) + testFilter(t, pdata.MetricDataTypeIntHistogram) + testFilter(t, pdata.MetricDataTypeDoubleHistogram) +} + +func testFilter(t *testing.T, mdType pdata.MetricDataType) { + format := "MetricName == '%s' && Label('%s') == '%s'" + q := fmt.Sprintf(format, filteredMetric, filteredLblKey, filteredLblVal) + + mds := testDataSlice(2, mdType) + totMetricCount := 0 + for _, md := range mds { + totMetricCount += md.MetricCount() + } + expectedMetricCount := totMetricCount - 1 + filtered := filterMetrics(t, nil, []string{q}, mds) + filteredMetricCount := 0 + for _, metrics := range filtered { + filteredMetricCount += metrics.MetricCount() + rmsSlice := metrics.ResourceMetrics() + for i := 0; i < rmsSlice.Len(); i++ { + rms := rmsSlice.At(i) + ilms := rms.InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + ilm := ilms.At(j) + metricSlice := ilm.Metrics() + for k := 0; k < metricSlice.Len(); k++ { + metric := metricSlice.At(k) + if metric.Name() == filteredMetric { + dt := metric.DataType() + switch dt { + case pdata.MetricDataTypeIntGauge: + pts := metric.IntGauge().DataPoints() + for l := 0; l < pts.Len(); l++ { + assertFiltered(t, pts.At(l).LabelsMap()) + } + case pdata.MetricDataTypeDoubleGauge: + pts := metric.DoubleGauge().DataPoints() + for l := 0; l < pts.Len(); l++ { + assertFiltered(t, pts.At(l).LabelsMap()) + } + case pdata.MetricDataTypeIntSum: + pts := metric.IntSum().DataPoints() + for l := 0; l < pts.Len(); l++ { + assertFiltered(t, pts.At(l).LabelsMap()) + } + case pdata.MetricDataTypeDoubleSum: + pts := metric.DoubleSum().DataPoints() + for l := 0; l < pts.Len(); l++ { + assertFiltered(t, pts.At(l).LabelsMap()) + } + case pdata.MetricDataTypeIntHistogram: + pts := metric.IntHistogram().DataPoints() + for l := 0; l < pts.Len(); l++ { + assertFiltered(t, pts.At(l).LabelsMap()) + } + case pdata.MetricDataTypeDoubleHistogram: + pts := metric.DoubleHistogram().DataPoints() + for l := 0; l < pts.Len(); l++ { + assertFiltered(t, pts.At(l).LabelsMap()) + } + } + } + } + } + } + } + assert.Equal(t, expectedMetricCount, filteredMetricCount) +} + +func assertFiltered(t *testing.T, lm pdata.StringMap) { + lm.ForEach(func(k string, v string) { + if k == filteredLblKey && v == filteredLblVal { + assert.Fail(t, "found metric that should have been filtered out") + } + }) +} + +func filterMetrics(t *testing.T, include []string, exclude []string, mds []pdata.Metrics) []pdata.Metrics { + proc, next, _ := testProcessor(t, include, exclude) + for _, md := range mds { + err := proc.ConsumeMetrics(context.Background(), md) + require.NoError(t, err) + } + return next.AllMetrics() +} + +func testProcessor(t *testing.T, include []string, exclude []string) (component.MetricsProcessor, *consumertest.MetricsSink, *observer.ObservedLogs) { + factory := NewFactory() + cfg := exprConfig(factory, include, exclude) + ctx := context.Background() + next := &consumertest.MetricsSink{} + core, logs := observer.New(zapcore.WarnLevel) + proc, err := factory.CreateMetricsProcessor( + ctx, + component.ProcessorCreateParams{ + Logger: zap.New(core), + }, + cfg, + next, + ) + require.NoError(t, err) + require.NotNil(t, proc) + return proc, next, logs +} + +func exprConfig(factory component.ProcessorFactory, include []string, exclude []string) configmodels.Processor { + cfg := factory.CreateDefaultConfig() + pCfg := cfg.(*Config) + pCfg.Metrics = MetricFilters{} + if include != nil { + pCfg.Metrics.Include = &filtermetric.MatchProperties{ + MatchType: "expr", + Expressions: include, + } + } + if exclude != nil { + pCfg.Metrics.Exclude = &filtermetric.MatchProperties{ + MatchType: "expr", + Expressions: exclude, + } + } + return cfg +} + +func testDataSlice(size int, mdType pdata.MetricDataType) []pdata.Metrics { + var out []pdata.Metrics + for i := 0; i < 16; i++ { + out = append(out, testData(fmt.Sprintf("p%d_", i), size, mdType)) + } + return out +} + +func testData(prefix string, size int, mdType pdata.MetricDataType) pdata.Metrics { + c := goldendataset.MetricCfg{ + MetricDescriptorType: mdType, + MetricNamePrefix: prefix, + NumILMPerResource: size, + NumMetricsPerILM: size, + NumPtLabels: size, + NumPtsPerMetric: size, + NumResourceAttrs: size, + NumResourceMetrics: size, + } + return goldendataset.MetricDataFromCfg(c) +} diff --git a/internal/otel_collector/processor/filterprocessor/factory.go b/internal/otel_collector/processor/filterprocessor/factory.go new file mode 100644 index 00000000000..383d16a2238 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/factory.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "filter" +) + +var processorCapabilities = component.ProcessorCapabilities{MutatesConsumedData: false} + +// NewFactory returns a new factory for the Filter processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithMetrics(createMetricsProcessor)) +} + +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createMetricsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsProcessor, error) { + fp, err := newFilterMetricProcessor(params.Logger, cfg.(*Config)) + if err != nil { + return nil, err + } + return processorhelper.NewMetricsProcessor( + cfg, + nextConsumer, + fp, + processorhelper.WithCapabilities(processorCapabilities)) +} diff --git a/internal/otel_collector/processor/filterprocessor/factory_test.go b/internal/otel_collector/processor/filterprocessor/factory_test.go new file mode 100644 index 00000000000..74e34f3a7a3 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/factory_test.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "context" + "fmt" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestType(t *testing.T) { + factory := NewFactory() + pType := factory.Type() + + assert.Equal(t, pType, configmodels.Type("filter")) +} + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.Equal(t, cfg, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + }) + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateProcessors(t *testing.T) { + tests := []struct { + configName string + succeed bool + }{ + { + configName: "config_regexp.yaml", + succeed: true, + }, { + configName: "config_strict.yaml", + succeed: true, + }, { + configName: "config_invalid.yaml", + succeed: false, + }, + } + + for _, test := range tests { + factories, err := componenttest.ExampleComponents() + assert.Nil(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", test.configName), factories) + assert.Nil(t, err) + + for name, cfg := range cfg.Processors { + t.Run(fmt.Sprintf("%s/%s", test.configName, name), func(t *testing.T) { + factory := NewFactory() + + tp, tErr := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewTracesNop()) + // Not implemented error + assert.NotNil(t, tErr) + assert.Nil(t, tp) + + mp, mErr := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewMetricsNop()) + assert.Equal(t, test.succeed, mp != nil) + assert.Equal(t, test.succeed, mErr == nil) + }) + } + } +} diff --git a/internal/otel_collector/processor/filterprocessor/filter_processor.go b/internal/otel_collector/processor/filterprocessor/filter_processor.go new file mode 100644 index 00000000000..fe544165393 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/filter_processor.go @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filtermetric" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +type filterMetricProcessor struct { + cfg *Config + include filtermetric.Matcher + exclude filtermetric.Matcher + logger *zap.Logger +} + +func newFilterMetricProcessor(logger *zap.Logger, cfg *Config) (*filterMetricProcessor, error) { + inc, err := createMatcher(cfg.Metrics.Include) + if err != nil { + return nil, err + } + + exc, err := createMatcher(cfg.Metrics.Exclude) + if err != nil { + return nil, err + } + + includeMatchType := "" + var includeExpressions []string + var includeMetricNames []string + if cfg.Metrics.Include != nil { + includeMatchType = string(cfg.Metrics.Include.MatchType) + includeExpressions = cfg.Metrics.Include.Expressions + includeMetricNames = cfg.Metrics.Include.MetricNames + } + + excludeMatchType := "" + var excludeExpressions []string + var excludeMetricNames []string + if cfg.Metrics.Exclude != nil { + excludeMatchType = string(cfg.Metrics.Exclude.MatchType) + excludeExpressions = cfg.Metrics.Exclude.Expressions + excludeMetricNames = cfg.Metrics.Exclude.MetricNames + } + + logger.Info( + "Metric filter configured", + zap.String("include match_type", includeMatchType), + zap.Strings("include expressions", includeExpressions), + zap.Strings("include metric names", includeMetricNames), + zap.String("exclude match_type", excludeMatchType), + zap.Strings("exclude expressions", excludeExpressions), + zap.Strings("exclude metric names", excludeMetricNames), + ) + + return &filterMetricProcessor{ + cfg: cfg, + include: inc, + exclude: exc, + logger: logger, + }, nil +} + +func createMatcher(mp *filtermetric.MatchProperties) (filtermetric.Matcher, error) { + // Nothing specified in configuration + if mp == nil { + return nil, nil + } + return filtermetric.NewMatcher(mp) +} + +// ProcessMetrics filters the given metrics based off the filterMetricProcessor's filters. +func (fmp *filterMetricProcessor) ProcessMetrics(_ context.Context, pdm pdata.Metrics) (pdata.Metrics, error) { + rms := pdm.ResourceMetrics() + idx := newMetricIndex() + for i := 0; i < rms.Len(); i++ { + ilms := rms.At(i).InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + ms := ilms.At(j).Metrics() + for k := 0; k < ms.Len(); k++ { + keep, err := fmp.shouldKeepMetric(ms.At(k)) + if err != nil { + fmp.logger.Error("shouldKeepMetric failed", zap.Error(err)) + // don't `continue`, keep the metric if there's an error + } + if keep { + idx.add(i, j, k) + } + } + } + } + if idx.isEmpty() { + return pdm, processorhelper.ErrSkipProcessingData + } + return idx.extract(pdm), nil +} + +func (fmp *filterMetricProcessor) shouldKeepMetric(metric pdata.Metric) (bool, error) { + if fmp.include != nil { + matches, err := fmp.include.MatchMetric(metric) + if err != nil { + // default to keep if there's an error + return true, err + } + if !matches { + return false, nil + } + } + + if fmp.exclude != nil { + matches, err := fmp.exclude.MatchMetric(metric) + if err != nil { + return true, err + } + if matches { + return false, nil + } + } + + return true, nil +} diff --git a/internal/otel_collector/processor/filterprocessor/filter_processor_test.go b/internal/otel_collector/processor/filterprocessor/filter_processor_test.go new file mode 100644 index 00000000000..a922a18b838 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/filter_processor_test.go @@ -0,0 +1,413 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "context" + "fmt" + "testing" + "time" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/internal/processor/filtermetric" + "go.opentelemetry.io/collector/translator/internaldata" +) + +type metricNameTest struct { + name string + inc *filtermetric.MatchProperties + exc *filtermetric.MatchProperties + inMN [][]*metricspb.Metric // input Metric batches + outMN [][]string // output Metric names + allMetricsFiltered bool +} + +var ( + validFilters = []string{ + "prefix/.*", + "prefix_.*", + ".*/suffix", + ".*_suffix", + ".*/contains/.*", + ".*_contains_.*", + "full/name/match", + "full_name_match", + } + + inMetricNames = []string{ + "full_name_match", + "not_exact_string_match", + "prefix/test/match", + "prefix_test_match", + "prefixprefix/test/match", + "test/match/suffix", + "test_match_suffix", + "test/match/suffixsuffix", + "test/contains/match", + "test_contains_match", + "random", + "full/name/match", + "full_name_match", // repeats + "not_exact_string_match", + } + + regexpMetricsFilterProperties = &filtermetric.MatchProperties{ + MatchType: filtermetric.Regexp, + MetricNames: validFilters, + } + + standardTests = []metricNameTest{ + { + name: "includeFilter", + inc: regexpMetricsFilterProperties, + inMN: [][]*metricspb.Metric{metricsWithName(inMetricNames)}, + outMN: [][]string{{ + "full_name_match", + "prefix/test/match", + "prefix_test_match", + "prefixprefix/test/match", + "test/match/suffix", + "test_match_suffix", + "test/match/suffixsuffix", + "test/contains/match", + "test_contains_match", + "full/name/match", + "full_name_match", + }}, + }, + { + name: "excludeFilter", + exc: regexpMetricsFilterProperties, + inMN: [][]*metricspb.Metric{metricsWithName(inMetricNames)}, + outMN: [][]string{{ + "not_exact_string_match", + "random", + "not_exact_string_match", + }}, + }, + { + name: "includeAndExclude", + inc: regexpMetricsFilterProperties, + exc: &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + MetricNames: []string{ + "prefix_test_match", + "test_contains_match", + }, + }, + inMN: [][]*metricspb.Metric{metricsWithName(inMetricNames)}, + outMN: [][]string{{ + "full_name_match", + "prefix/test/match", + // "prefix_test_match", excluded by exclude filter + "prefixprefix/test/match", + "test/match/suffix", + "test_match_suffix", + "test/match/suffixsuffix", + "test/contains/match", + // "test_contains_match", excluded by exclude filter + "full/name/match", + "full_name_match", + }}, + }, + { + name: "includeAndExcludeWithEmptyAndNil", + inc: regexpMetricsFilterProperties, + exc: &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + MetricNames: []string{ + "prefix_test_match", + "test_contains_match", + }, + }, + inMN: [][]*metricspb.Metric{nil, metricsWithName(inMetricNames), {}}, + outMN: [][]string{ + { + "full_name_match", + "prefix/test/match", + // "prefix_test_match", excluded by exclude filter + "prefixprefix/test/match", + "test/match/suffix", + "test_match_suffix", + "test/match/suffixsuffix", + "test/contains/match", + // "test_contains_match", excluded by exclude filter + "full/name/match", + "full_name_match", + }, + }, + }, + { + name: "emptyFilterInclude", + inc: &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + }, + inMN: [][]*metricspb.Metric{metricsWithName(inMetricNames)}, + allMetricsFiltered: true, + }, + { + name: "emptyFilterExclude", + exc: &filtermetric.MatchProperties{ + MatchType: filtermetric.Strict, + }, + inMN: [][]*metricspb.Metric{metricsWithName(inMetricNames)}, + outMN: [][]string{inMetricNames}, + }, + } +) + +func TestFilterMetricProcessor(t *testing.T) { + for _, test := range standardTests { + t.Run(test.name, func(t *testing.T) { + // next stores the results of the filter metric processor + next := new(consumertest.MetricsSink) + cfg := &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Metrics: MetricFilters{ + Include: test.inc, + Exclude: test.exc, + }, + } + factory := NewFactory() + fmp, err := factory.CreateMetricsProcessor( + context.Background(), + component.ProcessorCreateParams{ + Logger: zap.NewNop(), + }, + cfg, + next, + ) + assert.NotNil(t, fmp) + assert.Nil(t, err) + + caps := fmp.GetCapabilities() + assert.False(t, caps.MutatesConsumedData) + ctx := context.Background() + assert.NoError(t, fmp.Start(ctx, nil)) + + mds := make([]consumerdata.MetricsData, len(test.inMN)) + for i, metrics := range test.inMN { + mds[i] = consumerdata.MetricsData{ + Metrics: metrics, + } + } + cErr := fmp.ConsumeMetrics(context.Background(), internaldata.OCSliceToMetrics(mds)) + assert.Nil(t, cErr) + got := next.AllMetrics() + + if test.allMetricsFiltered { + require.Equal(t, 0, len(got)) + return + } + + require.Equal(t, 1, len(got)) + gotMD := internaldata.MetricsToOC(got[0]) + require.Equal(t, len(test.outMN), len(gotMD)) + for i, wantOut := range test.outMN { + assert.Equal(t, len(wantOut), len(gotMD[i].Metrics)) + for idx, out := range gotMD[i].Metrics { + assert.Equal(t, wantOut[idx], out.MetricDescriptor.Name) + } + } + assert.NoError(t, fmp.Shutdown(ctx)) + }) + } +} + +func metricsWithName(names []string) []*metricspb.Metric { + ret := make([]*metricspb.Metric, len(names)) + now := time.Now() + for i, name := range names { + ret[i] = &metricspb.Metric{ + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: name, + Type: metricspb.MetricDescriptor_GAUGE_INT64, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + { + Timestamp: timestamppb.New(now.Add(10 * time.Second)), + Value: &metricspb.Point_Int64Value{ + Int64Value: int64(123), + }, + }, + }, + }, + }, + } + } + return ret +} + +func BenchmarkStrictFilter(b *testing.B) { + mp := &filtermetric.MatchProperties{ + MatchType: "strict", + MetricNames: []string{"p10_metric_0"}, + } + benchmarkFilter(b, mp) +} + +func BenchmarkRegexpFilter(b *testing.B) { + mp := &filtermetric.MatchProperties{ + MatchType: "regexp", + MetricNames: []string{"p10_metric_0"}, + } + benchmarkFilter(b, mp) +} + +func BenchmarkExprFilter(b *testing.B) { + mp := &filtermetric.MatchProperties{ + MatchType: "expr", + Expressions: []string{`MetricName == "p10_metric_0"`}, + } + benchmarkFilter(b, mp) +} + +func benchmarkFilter(b *testing.B, mp *filtermetric.MatchProperties) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + pcfg := cfg.(*Config) + pcfg.Metrics = MetricFilters{ + Exclude: mp, + } + ctx := context.Background() + proc, _ := factory.CreateMetricsProcessor( + ctx, + component.ProcessorCreateParams{}, + cfg, + consumertest.NewMetricsNop(), + ) + pdms := metricSlice(128) + for i := 0; i < b.N; i++ { + for _, pdm := range pdms { + _ = proc.ConsumeMetrics(ctx, pdm) + } + } +} + +func metricSlice(numMetrics int) []pdata.Metrics { + var out []pdata.Metrics + for i := 0; i < numMetrics; i++ { + const size = 2 + out = append(out, pdm(fmt.Sprintf("p%d_", i), size)) + } + return out +} + +func pdm(prefix string, size int) pdata.Metrics { + c := goldendataset.MetricCfg{ + MetricDescriptorType: pdata.MetricDataTypeIntGauge, + MetricNamePrefix: prefix, + NumILMPerResource: size, + NumMetricsPerILM: size, + NumPtLabels: size, + NumPtsPerMetric: size, + NumResourceAttrs: size, + NumResourceMetrics: size, + } + return goldendataset.MetricDataFromCfg(c) +} + +func TestMetricIndexSingle(t *testing.T) { + metrics := pdm("", 1) + idx := newMetricIndex() + idx.add(0, 0, 0) + extracted := idx.extract(metrics) + require.Equal(t, metrics, extracted) +} + +func TestMetricIndexAll(t *testing.T) { + metrics := pdm("", 2) + idx := newMetricIndex() + idx.add(0, 0, 0) + idx.add(0, 0, 1) + idx.add(0, 1, 0) + idx.add(0, 1, 1) + idx.add(1, 0, 0) + idx.add(1, 0, 1) + idx.add(1, 1, 0) + idx.add(1, 1, 1) + extracted := idx.extract(metrics) + require.Equal(t, metrics, extracted) +} + +func TestNilResourceMetrics(t *testing.T) { + metrics := pdata.NewMetrics() + rms := metrics.ResourceMetrics() + rms.Append(pdata.NewResourceMetrics()) + requireNotPanics(t, metrics) +} + +func TestNilILM(t *testing.T) { + metrics := pdata.NewMetrics() + rms := metrics.ResourceMetrics() + rm := pdata.NewResourceMetrics() + rms.Append(rm) + ilms := rm.InstrumentationLibraryMetrics() + ilms.Append(pdata.NewInstrumentationLibraryMetrics()) + requireNotPanics(t, metrics) +} + +func TestNilMetric(t *testing.T) { + metrics := pdata.NewMetrics() + rms := metrics.ResourceMetrics() + rm := pdata.NewResourceMetrics() + rms.Append(rm) + ilms := rm.InstrumentationLibraryMetrics() + ilm := pdata.NewInstrumentationLibraryMetrics() + ilms.Append(ilm) + ms := ilm.Metrics() + ms.Append(pdata.NewMetric()) + requireNotPanics(t, metrics) +} + +func requireNotPanics(t *testing.T, metrics pdata.Metrics) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + pcfg := cfg.(*Config) + pcfg.Metrics = MetricFilters{ + Exclude: &filtermetric.MatchProperties{ + MatchType: "strict", + MetricNames: []string{"foo"}, + }, + } + ctx := context.Background() + proc, _ := factory.CreateMetricsProcessor( + ctx, + component.ProcessorCreateParams{ + Logger: zap.NewNop(), + }, + cfg, + consumertest.NewMetricsNop(), + ) + require.NotPanics(t, func() { + _ = proc.ConsumeMetrics(ctx, metrics) + }) +} diff --git a/internal/otel_collector/processor/filterprocessor/metric_index.go b/internal/otel_collector/processor/filterprocessor/metric_index.go new file mode 100644 index 00000000000..50afee55b86 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/metric_index.go @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filterprocessor + +import ( + "sort" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// metricIndex holds paths to metrics in a pdata.Metrics struct via the indexes +// ResourceMetrics -> InstrumentationLibraryMetrics -> Metrics. Once these +// indexes are populated, you can extract a pdata.Metrics from an existing +// pdata.Metrics with just the metrics at the specified paths. The motivation +// for this type is to allow the output of filtered metrics to not contain +// parent structs (InstrumentationLibrary, Resource, etc.) for a MetricSlice +// that has become empty after filtering. +type metricIndex struct { + m map[int]map[int]map[int]bool +} + +func newMetricIndex() *metricIndex { + return &metricIndex{m: map[int]map[int]map[int]bool{}} +} + +func (idx metricIndex) add(rmIdx, ilmIdx, mIdx int) { + rmMap, ok := idx.m[rmIdx] + if !ok { + rmMap = map[int]map[int]bool{} + idx.m[rmIdx] = rmMap + } + ilmMap, ok := rmMap[ilmIdx] + if !ok { + ilmMap = map[int]bool{} + rmMap[ilmIdx] = ilmMap + } + ilmMap[mIdx] = true +} + +func (idx metricIndex) extract(pdm pdata.Metrics) pdata.Metrics { + out := pdata.NewMetrics() + rmSliceOut := out.ResourceMetrics() + + sortRMIdx := sortRM(idx.m) + rmsIn := pdm.ResourceMetrics() + rmSliceOut.Resize(len(sortRMIdx)) + pos := 0 + for _, rmIdx := range sortRMIdx { + rmIn := rmsIn.At(rmIdx) + ilmSliceIn := rmIn.InstrumentationLibraryMetrics() + + rmOut := rmSliceOut.At(pos) + pos++ + rmIn.Resource().CopyTo(rmOut.Resource()) + ilmSliceOut := rmOut.InstrumentationLibraryMetrics() + ilmIndexes := idx.m[rmIdx] + for _, ilmIdx := range sortILM(ilmIndexes) { + ilmIn := ilmSliceIn.At(ilmIdx) + mSliceIn := ilmIn.Metrics() + + ilmOut := pdata.NewInstrumentationLibraryMetrics() + ilmSliceOut.Append(ilmOut) + ilOut := ilmOut.InstrumentationLibrary() + ilmIn.InstrumentationLibrary().CopyTo(ilOut) + mSliceOut := ilmOut.Metrics() + for _, metricIdx := range sortMetrics(ilmIndexes[ilmIdx]) { + mSliceOut.Append(mSliceIn.At(metricIdx)) + } + } + } + return out +} + +func sortRM(rmIndexes map[int]map[int]map[int]bool) []int { + var rmSorted = make([]int, len(rmIndexes)) + i := 0 + for key := range rmIndexes { + rmSorted[i] = key + i++ + } + sort.Ints(rmSorted) + return rmSorted +} + +func sortILM(ilmIndexes map[int]map[int]bool) []int { + var ilmSorted = make([]int, len(ilmIndexes)) + i := 0 + for key := range ilmIndexes { + ilmSorted[i] = key + i++ + } + sort.Ints(ilmSorted) + return ilmSorted +} + +func sortMetrics(metricIndexes map[int]bool) []int { + var metricIdxSorted = make([]int, len(metricIndexes)) + i := 0 + for key := range metricIndexes { + metricIdxSorted[i] = key + i++ + } + sort.Ints(metricIdxSorted) + return metricIdxSorted +} + +func (idx metricIndex) isEmpty() bool { + return len(idx.m) == 0 +} diff --git a/internal/otel_collector/processor/filterprocessor/testdata/config_expr.yaml b/internal/otel_collector/processor/filterprocessor/testdata/config_expr.yaml new file mode 100644 index 00000000000..01afa028747 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/testdata/config_expr.yaml @@ -0,0 +1,41 @@ +receivers: + examplereceiver: + +processors: + filter/empty: + metrics: + include: + match_type: expr + filter/include: + metrics: + include: + match_type: expr + expressions: + - Label("foo") == "bar" + - HasLabel("baz") + filter/exclude: + metrics: + exclude: + match_type: expr + expressions: + - Label("foo") == "bar" + - HasLabel("baz") + filter/includeexclude: + metrics: + include: + match_type: expr + expressions: + - HasLabel("foo") + exclude: + match_type: expr + expressions: + - HasLabel("bar") +exporters: + exampleexporter: + +service: + pipelines: + metrics: + receivers: [examplereceiver] + processors: [filter/empty] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/filterprocessor/testdata/config_invalid.yaml b/internal/otel_collector/processor/filterprocessor/testdata/config_invalid.yaml new file mode 100644 index 00000000000..a2ed32c8c40 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/testdata/config_invalid.yaml @@ -0,0 +1,26 @@ +receivers: + examplereceiver: + +processors: + filter/include: + # any names NOT matching filters are excluded from remainder of pipeline + metrics: + include: + match_type: regexp + metric_names: + # re2 regexp patterns + - (\W|^)stock\stips(\W|$ + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [filter/include] + exporters: [exampleexporter] + metrics: + receivers: [examplereceiver] + processors: [filter/include] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/filterprocessor/testdata/config_regexp.yaml b/internal/otel_collector/processor/filterprocessor/testdata/config_regexp.yaml new file mode 100644 index 00000000000..6ad5a90c1f7 --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/testdata/config_regexp.yaml @@ -0,0 +1,79 @@ +receivers: + examplereceiver: + +processors: + filter: + filter/include: + # any names NOT matching filters are excluded from remainder of pipeline + metrics: + include: + match_type: regexp + metric_names: + # re2 regexp patterns + - prefix/.* + - prefix_.* + - .*/suffix + - .*_suffix + - .*/contains/.* + - .*_contains_.* + - full/name/match + - full_name_match + filter/exclude: + # any names matching filters are excluded from remainder of pipeline + metrics: + exclude: + match_type: regexp + metric_names: + - prefix/.* + - prefix_.* + - .*/suffix + - .*_suffix + - .*/contains/.* + - .*_contains_.* + - full/name/match + - full_name_match + filter/unlimitedcache: + metrics: + include: + match_type: regexp + regexp: + cacheenabled: true + metric_names: + - prefix/.* + - prefix_.* + - .*/suffix + - .*_suffix + - .*/contains/.* + - .*_contains_.* + - full/name/match + - full_name_match + filter/limitedcache: + metrics: + exclude: + match_type: regexp + metric_names: + - prefix/.* + - prefix_.* + - .*/suffix + - .*_suffix + - .*/contains/.* + - .*_contains_.* + - full/name/match + - full_name_match + regexp: + cacheenabled: true + cachemaxnumentries: 10 + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [filter] + exporters: [exampleexporter] + metrics: + receivers: [examplereceiver] + processors: [filter] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/filterprocessor/testdata/config_strict.yaml b/internal/otel_collector/processor/filterprocessor/testdata/config_strict.yaml new file mode 100644 index 00000000000..5eddd3b6cdf --- /dev/null +++ b/internal/otel_collector/processor/filterprocessor/testdata/config_strict.yaml @@ -0,0 +1,51 @@ +receivers: + examplereceiver: + +processors: + filter/empty: + metrics: + include: + match_type: strict + filter/include: + metrics: + # any names NOT matching filters are excluded from remainder of pipeline + include: + match_type: strict + metric_names: + - hello_world + - hello/world + filter/exclude: + metrics: + # any names matching filters are excluded from remainder of pipeline + exclude: + match_type: strict + metric_names: + - hello_world + - hello/world + filter/includeexclude: + metrics: + # if both include and exclude are specified, include filters are applied first + # the following configuration would only allow metrics named "hello/world" to pass through + include: + match_type: strict + metric_names: + - hello_world + - hello/world + exclude: + match_type: strict + metric_names: + - hello_world + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [filter/empty] + exporters: [exampleexporter] + metrics: + receivers: [examplereceiver] + processors: [filter/empty] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/memorylimiter/README.md b/internal/otel_collector/processor/memorylimiter/README.md new file mode 100644 index 00000000000..0b5008c5463 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/README.md @@ -0,0 +1,107 @@ +# Memory Limiter Processor + +Supported pipeline types: metrics, traces + +The memory limiter processor is used to prevent out of memory situations on +the collector. Given that the amount and type of data the collector processes is +environment specific and resource utilization of the collector is also dependent +on the configured processors, it is important to put checks in place regarding +memory usage. + +The memory_limiter processor allows to perform periodic checks of memory +usage if it exceeds defined limits will begin dropping data and forcing GC to reduce +memory consumption. + +The memory_limiter uses soft and hard memory limits. Hard limit is always above or equal +the soft limit. + +When the memory usage exceeds the soft limit the processor will start dropping the data and +return errors to the preceding component it in the pipeline (which should be normally a +receiver). + +When the memory usage is above the hard limit in addition to dropping the data the +processor will forcedly perform garbage collection in order to try to free memory. + +When the memory usage drop below the soft limit, the normal operation is resumed (data +will not longer be dropped and no forced garbage collection will be performed). + +The difference between the soft limit and hard limits is defined via `spike_limit_mib` +configuration option. The value of this option should be selected in a way that ensures +that between the memory check intervals the memory usage cannot increase by more than this +value (otherwise memory usage may exceed the hard limit - even if temporarily). +A good starting point for `spike_limit_mib` is 20% of the hard limit. Bigger +`spike_limit_mib` values may be necessary for spiky traffic or for longer check intervals. + +In addition, if the command line option `mem-ballast-size-mib` is used to specify a +ballast (see command line help for details), the same value that is provided via the +command line must also be defined in the memory_limiter processor using `ballast_size_mib` +config option. If the command line option value and config option value don't match +the behavior of the memory_limiter processor will be unpredictable. + +Note that while the processor can help mitigate out of memory situations, +it is not a replacement for properly sizing and configuring the +collector. Keep in mind that if the soft limit is crossed, the collector will +return errors to all receive operations until enough memory is freed. This will +result in dropped data. + +It is highly recommended to configure the ballast command line option as well as the +memory_limiter processor on every collector. The ballast should be configured to +be 1/3 to 1/2 of the memory allocated to the collector. The memory_limiter +processor should be the first processor defined in the pipeline (immediately after +the receivers). This is to ensure that backpressure can be sent to applicable +receivers and minimize the likelihood of dropped data when the memory_limiter gets +triggered. + +Please refer to [config.go](./config.go) for the config spec. + +The following configuration options **must be changed**: +- `check_interval` (default = 0s): Time between measurements of memory +usage. The recommended value is 1 second. +If the expected traffic to the Collector is very spiky then decrease the `check_interval` +or increase `spike_limit_mib` to avoid memory usage going over the hard limit. +- `limit_mib` (default = 0): Maximum amount of memory, in MiB, targeted to be +allocated by the process heap. Note that typically the total memory usage of +process will be about 50MiB higher than this value. This defines the hard limit. +- `spike_limit_mib` (default = 20% of `limit_mib`): Maximum spike expected between the +measurements of memory usage. The value must be less than `limit_mib`. The soft limit +value will be equal to (limit_mib - spike_limit_mib). +The recommended value for `spike_limit_mib` is about 20% `limit_mib`. +- `limit_percentage` (default = 0): Maximum amount of total memory targeted to be +allocated by the process heap. This configuration is supported on Linux systems with cgroups +and it's intended to be used in dynamic platforms like docker. +This option is used to calculate `memory_limit` from the total available memory. +For instance setting of 75% with the total memory of 1GiB will result in the limit of 750 MiB. +The fixed memory setting (`limit_mib`) takes precedence +over the percentage configuration. +- `spike_limit_percentage` (default = 0): Maximum spike expected between the +measurements of memory usage. The value must be less than `limit_percentage`. +This option is used to calculate `spike_limit_mib` from the total available memory. +For instance setting of 25% with the total memory of 1GiB will result in the spike limit of 250MiB. +This option is intended to be used only with `limit_percentage`. + +The following configuration options can also be modified: +- `ballast_size_mib` (default = 0): Must match the `mem-ballast-size-mib` +command line option. + +Examples: + +```yaml +processors: + memory_limiter: + ballast_size_mib: 2000 + check_interval: 1s + limit_mib: 4000 + spike_limit_mib: 800 +``` + +```yaml +processors: + memory_limiter: + ballast_size_mib: 2000 + check_interval: 1s + limit_percentage: 50 + spike_limit_percentage: 30 +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. diff --git a/internal/otel_collector/processor/memorylimiter/config.go b/internal/otel_collector/processor/memorylimiter/config.go new file mode 100644 index 00000000000..f750d902378 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/config.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package memorylimiter provides a processor for OpenTelemetry Service pipeline +// that drops data on the pipeline according to the current state of memory +// usage. +package memorylimiter + +import ( + "time" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for memory memoryLimiter processor. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + + // CheckInterval is the time between measurements of memory usage for the + // purposes of avoiding going over the limits. Defaults to zero, so no + // checks will be performed. + CheckInterval time.Duration `mapstructure:"check_interval"` + + // MemoryLimitMiB is the maximum amount of memory, in MiB, targeted to be + // allocated by the process. + MemoryLimitMiB uint32 `mapstructure:"limit_mib"` + + // MemorySpikeLimitMiB is the maximum, in MiB, spike expected between the + // measurements of memory usage. + MemorySpikeLimitMiB uint32 `mapstructure:"spike_limit_mib"` + + // BallastSizeMiB is the size, in MiB, of the ballast size being used by the + // process. + BallastSizeMiB uint32 `mapstructure:"ballast_size_mib"` + + // MemoryLimitPercentage is the maximum amount of memory, in %, targeted to be + // allocated by the process. The fixed memory settings MemoryLimitMiB has a higher precedence. + MemoryLimitPercentage uint32 `mapstructure:"limit_percentage"` + // MemorySpikePercentage is the maximum, in percents against the total memory, + // spike expected between the measurements of memory usage. + MemorySpikePercentage uint32 `mapstructure:"spike_limit_percentage"` +} + +// Name of BallastSizeMiB config option. +const ballastSizeMibKey = "ballast_size_mib" diff --git a/internal/otel_collector/processor/memorylimiter/config_test.go b/internal/otel_collector/processor/memorylimiter/config_test.go new file mode 100644 index 00000000000..5d513f0c087 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/config_test.go @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memorylimiter + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + require.NoError(t, err) + factory := NewFactory() + factories.Processors[typeStr] = factory + require.NoError(t, err) + + cfg, err := configtest.LoadConfigFile( + t, + path.Join(".", "testdata", "config.yaml"), + factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + p0 := cfg.Processors["memory_limiter"] + assert.Equal(t, p0, + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "memory_limiter", + NameVal: "memory_limiter", + }, + }) + + p1 := cfg.Processors["memory_limiter/with-settings"] + assert.Equal(t, p1, + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "memory_limiter", + NameVal: "memory_limiter/with-settings", + }, + CheckInterval: 5 * time.Second, + MemoryLimitMiB: 4000, + MemorySpikeLimitMiB: 500, + BallastSizeMiB: 2000, + }) +} diff --git a/internal/otel_collector/processor/memorylimiter/factory.go b/internal/otel_collector/processor/memorylimiter/factory.go new file mode 100644 index 00000000000..401957a42c4 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/factory.go @@ -0,0 +1,106 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memorylimiter + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" Attribute Key in configuration. + typeStr = "memory_limiter" +) + +var processorCapabilities = component.ProcessorCapabilities{MutatesConsumedData: false} + +// NewFactory returns a new factory for the Memory Limiter processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor), + processorhelper.WithMetrics(createMetricsProcessor), + processorhelper.WithLogs(createLogsProcessor)) +} + +// CreateDefaultConfig creates the default configuration for processor. Notice +// that the default configuration is expected to fail for this processor. +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createTraceProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, +) (component.TracesProcessor, error) { + ml, err := newMemoryLimiter(params.Logger, cfg.(*Config)) + if err != nil { + return nil, err + } + return processorhelper.NewTraceProcessor( + cfg, + nextConsumer, + ml, + processorhelper.WithCapabilities(processorCapabilities), + processorhelper.WithShutdown(ml.shutdown)) +} + +func createMetricsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsProcessor, error) { + ml, err := newMemoryLimiter(params.Logger, cfg.(*Config)) + if err != nil { + return nil, err + } + return processorhelper.NewMetricsProcessor( + cfg, + nextConsumer, + ml, + processorhelper.WithCapabilities(processorCapabilities), + processorhelper.WithShutdown(ml.shutdown)) +} + +func createLogsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.LogsConsumer, +) (component.LogsProcessor, error) { + ml, err := newMemoryLimiter(params.Logger, cfg.(*Config)) + if err != nil { + return nil, err + } + return processorhelper.NewLogsProcessor( + cfg, + nextConsumer, + ml, + processorhelper.WithCapabilities(processorCapabilities), + processorhelper.WithShutdown(ml.shutdown)) +} diff --git a/internal/otel_collector/processor/memorylimiter/factory_test.go b/internal/otel_collector/processor/memorylimiter/factory_test.go new file mode 100644 index 00000000000..c7ee2eba04a --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/factory_test.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memorylimiter + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + require.NotNil(t, factory) + + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateProcessor(t *testing.T) { + factory := NewFactory() + require.NotNil(t, factory) + + cfg := factory.CreateDefaultConfig() + + // This processor can't be created with the default config. + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewTracesNop()) + assert.Nil(t, tp) + assert.Error(t, err, "created processor with invalid settings") + + mp, err := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewMetricsNop()) + assert.Nil(t, mp) + assert.Error(t, err, "created processor with invalid settings") + + lp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewLogsNop()) + assert.Nil(t, lp) + assert.Error(t, err, "created processor with invalid settings") + + // Create processor with a valid config. + pCfg := cfg.(*Config) + pCfg.MemoryLimitMiB = 5722 + pCfg.MemorySpikeLimitMiB = 1907 + pCfg.BallastSizeMiB = 2048 + pCfg.CheckInterval = 100 * time.Millisecond + + tp, err = factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewTracesNop()) + assert.NoError(t, err) + assert.NotNil(t, tp) + assert.NoError(t, tp.Shutdown(context.Background())) + + mp, err = factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewMetricsNop()) + assert.NoError(t, err) + assert.NotNil(t, mp) + assert.NoError(t, mp.Shutdown(context.Background())) + + lp, err = factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewLogsNop()) + assert.NoError(t, err) + assert.NotNil(t, lp) + assert.NoError(t, lp.Shutdown(context.Background())) +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup.go new file mode 100644 index 00000000000..9af9e4cb90a --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "bufio" + "io" + "os" + "path/filepath" + "strconv" +) + +// CGroup represents the data structure for a Linux control group. +type CGroup struct { + path string +} + +// NewCGroup returns a new *CGroup from a given path. +func NewCGroup(path string) *CGroup { + return &CGroup{path: path} +} + +// Path returns the path of the CGroup*. +func (cg *CGroup) Path() string { + return cg.path +} + +// ParamPath returns the path of the given cgroup param under itself. +func (cg *CGroup) ParamPath(param string) string { + return filepath.Join(cg.path, param) +} + +// readFirstLine reads the first line from a cgroup param file. +func (cg *CGroup) readFirstLine(param string) (string, error) { + paramFile, err := os.Open(cg.ParamPath(param)) + if err != nil { + return "", err + } + defer paramFile.Close() + + scanner := bufio.NewScanner(paramFile) + if scanner.Scan() { + return scanner.Text(), nil + } + if err := scanner.Err(); err != nil { + return "", err + } + return "", io.ErrUnexpectedEOF +} + +// readInt parses the first line from a cgroup param file as int. +func (cg *CGroup) readInt(param string) (int, error) { + text, err := cg.readFirstLine(param) + if err != nil { + return 0, err + } + return strconv.Atoi(text) +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup_test.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup_test.go new file mode 100644 index 00000000000..eae5b13788e --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroup_test.go @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCGroupParamPath(t *testing.T) { + cgroup := NewCGroup("/sys/fs/cgroup/cpu") + assert.Equal(t, "/sys/fs/cgroup/cpu", cgroup.Path()) + assert.Equal(t, "/sys/fs/cgroup/cpu/cpu.cfs_quota_us", cgroup.ParamPath("cpu.cfs_quota_us")) +} + +func TestCGroupReadFirstLine(t *testing.T) { + testTable := []struct { + name string + paramName string + expectedContent string + shouldHaveError bool + }{ + { + name: "cpu", + paramName: "cpu.cfs_period_us", + expectedContent: "100000", + shouldHaveError: false, + }, + { + name: "absent", + paramName: "cpu.stat", + expectedContent: "", + shouldHaveError: true, + }, + { + name: "empty", + paramName: "cpu.cfs_quota_us", + expectedContent: "", + shouldHaveError: true, + }, + } + + for _, tt := range testTable { + cgroupPath := filepath.Join(testDataCGroupsPath, tt.name) + cgroup := NewCGroup(cgroupPath) + + content, err := cgroup.readFirstLine(tt.paramName) + assert.Equal(t, tt.expectedContent, content, tt.name) + + if tt.shouldHaveError { + assert.Error(t, err, tt.name) + } else { + assert.NoError(t, err, tt.name) + } + } +} + +func TestCGroupReadInt(t *testing.T) { + testTable := []struct { + name string + paramName string + expectedValue int + shouldHaveError bool + }{ + { + name: "cpu", + paramName: "cpu.cfs_period_us", + expectedValue: 100000, + shouldHaveError: false, + }, + { + name: "empty", + paramName: "cpu.cfs_quota_us", + expectedValue: 0, + shouldHaveError: true, + }, + { + name: "invalid", + paramName: "cpu.cfs_quota_us", + expectedValue: 0, + shouldHaveError: true, + }, + { + name: "absent", + paramName: "cpu.cfs_quota_us", + expectedValue: 0, + shouldHaveError: true, + }, + } + + for _, tt := range testTable { + cgroupPath := filepath.Join(testDataCGroupsPath, tt.name) + cgroup := NewCGroup(cgroupPath) + + value, err := cgroup.readInt(tt.paramName) + assert.Equal(t, tt.expectedValue, value, "%s/%s", tt.name, tt.paramName) + + if tt.shouldHaveError { + assert.Error(t, err, tt.name) + } else { + assert.NoError(t, err, tt.name) + } + } +} + +func TestCGroupMemory(t *testing.T) { + process, err := NewCGroupsForCurrentProcess() + require.NoError(t, err) + quota, b, err := process.MemoryQuota() + require.True(t, b) + require.NoError(t, err) + fmt.Println(quota) +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups.go new file mode 100644 index 00000000000..27adbef7353 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups.go @@ -0,0 +1,122 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +const ( + // _cgroupFSType is the Linux CGroup file system type used in + // `/proc/$PID/mountinfo`. + _cgroupFSType = "cgroup" + // _cgroupSubsysCPU is the CPU CGroup subsystem. + _cgroupSubsysCPU = "cpu" + // _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem. + _cgroupSubsysCPUAcct = "cpuacct" + // _cgroupSubsysCPUSet is the CPUSet CGroup subsystem. + _cgroupSubsysCPUSet = "cpuset" + // _cgroupSubsysMemory is the Memory CGroup subsystem. + _cgroupSubsysMemory = "memory" + + _cgroupMemoryLimitBytes = "memory.limit_in_bytes" +) + +const ( + _procPathCGroup = "/proc/self/cgroup" + _procPathMountInfo = "/proc/self/mountinfo" +) + +// CGroups is a map that associates each CGroup with its subsystem name. +type CGroups map[string]*CGroup + +// NewCGroups returns a new *CGroups from given `mountinfo` and `cgroup` files +// under for some process under `/proc` file system (see also proc(5) for more +// information). +func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) { + cgroupSubsystems, err := parseCGroupSubsystems(procPathCGroup) + if err != nil { + return nil, err + } + + cgroups := make(CGroups) + newMountPoint := func(mp *MountPoint) error { + if mp.FSType != _cgroupFSType { + return nil + } + + for _, opt := range mp.SuperOptions { + subsys, exists := cgroupSubsystems[opt] + if !exists { + continue + } + + cgroupPath, err := mp.Translate(subsys.Name) + if err != nil { + return err + } + cgroups[opt] = NewCGroup(cgroupPath) + } + + return nil + } + + if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { + return nil, err + } + return cgroups, nil +} + +// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current +// process. +func NewCGroupsForCurrentProcess() (CGroups, error) { + return NewCGroups(_procPathMountInfo, _procPathCGroup) +} + +// MemoryQuota returns the total memory a +// It is a result of `memory.limit_in_bytes`. If the value of +// `memory.limit_in_bytes` was not set (-1), the method returns `(-1, false, nil)`. +func (cg CGroups) MemoryQuota() (int64, bool, error) { + cpuCGroup, exists := cg[_cgroupSubsysMemory] + if !exists { + return -1, false, nil + } + + memLimitBytes, err := cpuCGroup.readInt(_cgroupMemoryLimitBytes) + if defined := memLimitBytes > 0; err != nil || !defined { + return -1, defined, err + } + return int64(memLimitBytes), true, nil +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups_test.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups_test.go new file mode 100644 index 00000000000..8f81db70aca --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/cgroups_test.go @@ -0,0 +1,137 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewCGroups(t *testing.T) { + cgroupsProcCGroupPath := filepath.Join(testDataProcPath, "cgroups", "cgroup") + cgroupsProcMountInfoPath := filepath.Join(testDataProcPath, "cgroups", "mountinfo") + + testTable := []struct { + subsys string + path string + }{ + {_cgroupSubsysCPU, "/sys/fs/cgroup/cpu,cpuacct"}, + {_cgroupSubsysCPUAcct, "/sys/fs/cgroup/cpu,cpuacct"}, + {_cgroupSubsysCPUSet, "/sys/fs/cgroup/cpuset"}, + {_cgroupSubsysMemory, "/sys/fs/cgroup/memory/large"}, + } + + cgroups, err := NewCGroups(cgroupsProcMountInfoPath, cgroupsProcCGroupPath) + assert.Equal(t, len(testTable), len(cgroups)) + assert.NoError(t, err) + + for _, tt := range testTable { + cgroup, exists := cgroups[tt.subsys] + assert.True(t, exists, "%q expected to present in `cgroups`", tt.subsys) + assert.Equal(t, tt.path, cgroup.path, "%q expected for `cgroups[%q].path`, got %q", tt.path, tt.subsys, cgroup.path) + } +} + +func TestNewCGroupsWithErrors(t *testing.T) { + testTable := []struct { + mountInfoPath string + cgroupPath string + }{ + {"non-existing-file", "/dev/null"}, + {"/dev/null", "non-existing-file"}, + { + "/dev/null", + filepath.Join(testDataProcPath, "invalid-cgroup", "cgroup"), + }, + { + filepath.Join(testDataProcPath, "invalid-mountinfo", "mountinfo"), + "/dev/null", + }, + { + filepath.Join(testDataProcPath, "untranslatable", "mountinfo"), + filepath.Join(testDataProcPath, "untranslatable", "cgroup"), + }, + } + + for _, tt := range testTable { + cgroups, err := NewCGroups(tt.mountInfoPath, tt.cgroupPath) + assert.Nil(t, cgroups) + assert.Error(t, err) + } +} + +func TestCGroupsCPUQuota(t *testing.T) { + testTable := []struct { + name string + expectedQuota int64 + expectedDefined bool + shouldHaveError bool + }{ + { + name: "undefined", + expectedQuota: int64(-1.0), + expectedDefined: false, + shouldHaveError: false, + }, + } + + cgroups := make(CGroups) + + quota, defined, err := cgroups.MemoryQuota() + assert.Equal(t, int64(-1), quota, "nonexistent") + assert.False(t, defined, "nonexistent") + assert.NoError(t, err, "nonexistent") + + for _, tt := range testTable { + cgroupPath := filepath.Join(testDataCGroupsPath, tt.name) + cgroups[_cgroupSubsysCPU] = NewCGroup(cgroupPath) + + quota, defined, err := cgroups.MemoryQuota() + assert.Equal(t, tt.expectedQuota, quota, tt.name) + assert.Equal(t, tt.expectedDefined, defined, tt.name) + + if tt.shouldHaveError { + assert.Error(t, err, tt.name) + } else { + assert.NoError(t, err, tt.name) + } + } +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/doc.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/doc.go new file mode 100644 index 00000000000..ed5c98c7ec0 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/doc.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package cgroups provides utilities to access Linux control group (CGroups) +// parameters (total memory, for example) for a given process. +// The original implementation is taken from https://github.com/uber-go/automaxprocs +package cgroups diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/errors.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/errors.go new file mode 100644 index 00000000000..d9681758464 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/errors.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import "fmt" + +type cgroupSubsysFormatInvalidError struct { + line string +} + +type mountPointFormatInvalidError struct { + line string +} + +type pathNotExposedFromMountPointError struct { + mountPoint string + root string + path string +} + +func (err cgroupSubsysFormatInvalidError) Error() string { + return fmt.Sprintf("invalid format for CGroupSubsys: %q", err.line) +} + +func (err mountPointFormatInvalidError) Error() string { + return fmt.Sprintf("invalid format for MountPoint: %q", err.line) +} + +func (err pathNotExposedFromMountPointError) Error() string { + return fmt.Sprintf("path %q is not a descendant of mount point root %q and cannot be exposed from %q", err.path, err.root, err.mountPoint) +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint.go new file mode 100644 index 00000000000..1fe78f43316 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint.go @@ -0,0 +1,182 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "bufio" + "os" + "path/filepath" + "strconv" + "strings" +) + +const ( + _mountInfoSep = " " + _mountInfoOptsSep = "," + _mountInfoOptionalFieldsSep = "-" +) + +const ( + _miFieldIDMountID = iota + _miFieldIDParentID + _miFieldIDDeviceID + _miFieldIDRoot + _miFieldIDMountPoint + _miFieldIDOptions + _miFieldIDOptionalFields + + _miFieldCountFirstHalf +) + +const ( + _miFieldOffsetFSType = iota + _miFieldOffsetMountSource + _miFieldOffsetSuperOptions + + _miFieldCountSecondHalf +) + +const _miFieldCountMin = _miFieldCountFirstHalf + _miFieldCountSecondHalf + +// MountPoint is the data structure for the mount points in +// `/proc/$PID/mountinfo`. See also proc(5) for more information. +type MountPoint struct { + MountID int + ParentID int + DeviceID string + Root string + MountPoint string + Options []string + OptionalFields []string + FSType string + MountSource string + SuperOptions []string +} + +// NewMountPointFromLine parses a line read from `/proc/$PID/mountinfo` and +// returns a new *MountPoint. +func NewMountPointFromLine(line string) (*MountPoint, error) { + fields := strings.Split(line, _mountInfoSep) + + if len(fields) < _miFieldCountMin { + return nil, mountPointFormatInvalidError{line} + } + + mountID, err := strconv.Atoi(fields[_miFieldIDMountID]) + if err != nil { + return nil, err + } + + parentID, err := strconv.Atoi(fields[_miFieldIDParentID]) + if err != nil { + return nil, err + } + + for i, field := range fields[_miFieldIDOptionalFields:] { + if field == _mountInfoOptionalFieldsSep { + fsTypeStart := _miFieldIDOptionalFields + i + 1 + + if len(fields) != fsTypeStart+_miFieldCountSecondHalf { + return nil, mountPointFormatInvalidError{line} + } + + miFieldIDFSType := _miFieldOffsetFSType + fsTypeStart + miFieldIDMountSource := _miFieldOffsetMountSource + fsTypeStart + miFieldIDSuperOptions := _miFieldOffsetSuperOptions + fsTypeStart + + return &MountPoint{ + MountID: mountID, + ParentID: parentID, + DeviceID: fields[_miFieldIDDeviceID], + Root: fields[_miFieldIDRoot], + MountPoint: fields[_miFieldIDMountPoint], + Options: strings.Split(fields[_miFieldIDOptions], _mountInfoOptsSep), + OptionalFields: fields[_miFieldIDOptionalFields:(fsTypeStart - 1)], + FSType: fields[miFieldIDFSType], + MountSource: fields[miFieldIDMountSource], + SuperOptions: strings.Split(fields[miFieldIDSuperOptions], _mountInfoOptsSep), + }, nil + } + } + + return nil, mountPointFormatInvalidError{line} +} + +// Translate converts an absolute path inside the *MountPoint's file system to +// the host file system path in the mount namespace the *MountPoint belongs to. +func (mp *MountPoint) Translate(absPath string) (string, error) { + relPath, err := filepath.Rel(mp.Root, absPath) + + if err != nil { + return "", err + } + if relPath == ".." || strings.HasPrefix(relPath, "../") { + return "", pathNotExposedFromMountPointError{ + mountPoint: mp.MountPoint, + root: mp.Root, + path: absPath, + } + } + + return filepath.Join(mp.MountPoint, relPath), nil +} + +// parseMountInfo parses procPathMountInfo (usually at `/proc/$PID/mountinfo`) +// and yields parsed *MountPoint into newMountPoint. +func parseMountInfo(procPathMountInfo string, newMountPoint func(*MountPoint) error) error { + mountInfoFile, err := os.Open(procPathMountInfo) + if err != nil { + return err + } + defer mountInfoFile.Close() + + scanner := bufio.NewScanner(mountInfoFile) + + for scanner.Scan() { + mountPoint, err := NewMountPointFromLine(scanner.Text()) + if err != nil { + return err + } + if err := newMountPoint(mountPoint); err != nil { + return err + } + } + + return scanner.Err() +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint_test.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint_test.go new file mode 100644 index 00000000000..0fd9749c5e1 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/mountpoint_test.go @@ -0,0 +1,199 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMountPointFromLine(t *testing.T) { + testTable := []struct { + name string + line string + expected *MountPoint + }{ + { + name: "root", + line: "1 0 252:0 / / rw,noatime - ext4 /dev/dm-0 rw,errors=remount-ro,data=ordered", + expected: &MountPoint{ + MountID: 1, + ParentID: 0, + DeviceID: "252:0", + Root: "/", + MountPoint: "/", + Options: []string{"rw", "noatime"}, + OptionalFields: []string{}, + FSType: "ext4", + MountSource: "/dev/dm-0", + SuperOptions: []string{"rw", "errors=remount-ro", "data=ordered"}, + }, + }, + { + name: "cgroup", + line: "31 23 0:24 /docker /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime shared:1 - cgroup cgroup rw,cpu", + expected: &MountPoint{ + MountID: 31, + ParentID: 23, + DeviceID: "0:24", + Root: "/docker", + MountPoint: "/sys/fs/cgroup/cpu", + Options: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, + OptionalFields: []string{"shared:1"}, + FSType: "cgroup", + MountSource: "cgroup", + SuperOptions: []string{"rw", "cpu"}, + }, + }, + } + + for _, tt := range testTable { + mountPoint, err := NewMountPointFromLine(tt.line) + assert.Equal(t, tt.expected, mountPoint, tt.name) + assert.NoError(t, err, tt.name) + } +} + +func TestNewMountPointFromLineErr(t *testing.T) { + linesWithInvalidIDs := []string{ + "invalidMountID 0 252:0 / / rw,noatime - ext4 /dev/dm-0 rw,errors=remount-ro,data=ordered", + "1 invalidParentID 252:0 / / rw,noatime - ext4 /dev/dm-0 rw,errors=remount-ro,data=ordered", + "invalidMountID invalidParentID 252:0 / / rw,noatime - ext4 /dev/dm-0 rw,errors=remount-ro,data=ordered", + } + + for i, line := range linesWithInvalidIDs { + mountPoint, err := NewMountPointFromLine(line) + assert.Nil(t, mountPoint, "[%d] %q", i, line) + assert.Error(t, err, line) + } + + linesWithInvalidFields := []string{ + "1 0 252:0 / / rw,noatime ext4 /dev/dm-0 rw,errors=remount-ro,data=ordered", + "1 0 252:0 / / rw,noatime shared:1 - ext4 /dev/dm-0", + "1 0 252:0 / / rw,noatime shared:1 ext4 - /dev/dm-0 rw,errors=remount-ro,data=ordered", + "1 0 252:0 / / rw,noatime shared:1 ext4 /dev/dm-0 rw,errors=remount-ro,data=ordered", + "random line", + } + + for i, line := range linesWithInvalidFields { + mountPoint, err := NewMountPointFromLine(line) + errExpected := mountPointFormatInvalidError{line} + + assert.Nil(t, mountPoint, "[%d] %q", i, line) + assert.Equal(t, err, errExpected, "[%d] %q", i, line) + } +} + +func TestMountPointTranslate(t *testing.T) { + line := "31 23 0:24 /docker/0123456789abcdef /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime shared:1 - cgroup cgroup rw,cpu" + cgroupMountPoint, err := NewMountPointFromLine(line) + + assert.NotNil(t, cgroupMountPoint) + assert.NoError(t, err) + + testTable := []struct { + name string + pathToTranslate string + pathTranslated string + }{ + { + name: "root", + pathToTranslate: "/docker/0123456789abcdef", + pathTranslated: "/sys/fs/cgroup/cpu", + }, + { + name: "root-with-extra-slash", + pathToTranslate: "/docker/0123456789abcdef/", + pathTranslated: "/sys/fs/cgroup/cpu", + }, + { + name: "descendant-from-root", + pathToTranslate: "/docker/0123456789abcdef/large/cpu.cfs_quota_us", + pathTranslated: "/sys/fs/cgroup/cpu/large/cpu.cfs_quota_us", + }, + } + + for _, tt := range testTable { + path, err := cgroupMountPoint.Translate(tt.pathToTranslate) + assert.Equal(t, tt.pathTranslated, path, tt.name) + assert.NoError(t, err, tt.name) + } +} + +func TestMountPointTranslateError(t *testing.T) { + line := "31 23 0:24 /docker/0123456789abcdef /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime shared:1 - cgroup cgroup rw,cpu" + cgroupMountPoint, err := NewMountPointFromLine(line) + + assert.NotNil(t, cgroupMountPoint) + assert.NoError(t, err) + + inaccessiblePaths := []string{ + "/", + "/docker", + "/docker/0123456789abcdef-let-me-hack-this-path", + "/docker/0123456789abcde/abc/../../def", + "/system.slice/docker.service", + } + + for i, path := range inaccessiblePaths { + translated, err := cgroupMountPoint.Translate(path) + errExpected := pathNotExposedFromMountPointError{ + mountPoint: cgroupMountPoint.MountPoint, + root: cgroupMountPoint.Root, + path: path, + } + + assert.Equal(t, "", translated, "inaccessiblePaths[%d] == %q", i, path) + assert.Equal(t, errExpected, err, "inaccessiblePaths[%d] == %q", i, path) + } + + relPaths := []string{ + "docker", + "docker/0123456789abcde/large", + "system.slice/docker.service", + } + + for i, path := range relPaths { + translated, err := cgroupMountPoint.Translate(path) + + assert.Equal(t, "", translated, "relPaths[%d] == %q", i, path) + assert.Error(t, err, path) + } +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys.go new file mode 100644 index 00000000000..b92119c501e --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys.go @@ -0,0 +1,118 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "bufio" + "os" + "strconv" + "strings" +) + +const ( + _cgroupSep = ":" + _cgroupSubsysSep = "," +) + +const ( + _csFieldIDID = iota + _csFieldIDSubsystems + _csFieldIDName + _csFieldCount +) + +// CGroupSubsys represents the data structure for entities in +// `/proc/$PID/cgroup`. See also proc(5) for more information. +type CGroupSubsys struct { + ID int + Subsystems []string + Name string +} + +// NewCGroupSubsysFromLine returns a new *CGroupSubsys by parsing a string in +// the format of `/proc/$PID/cgroup` +func NewCGroupSubsysFromLine(line string) (*CGroupSubsys, error) { + fields := strings.Split(line, _cgroupSep) + + if len(fields) != _csFieldCount { + return nil, cgroupSubsysFormatInvalidError{line} + } + + id, err := strconv.Atoi(fields[_csFieldIDID]) + if err != nil { + return nil, err + } + + cgroup := &CGroupSubsys{ + ID: id, + Subsystems: strings.Split(fields[_csFieldIDSubsystems], _cgroupSubsysSep), + Name: fields[_csFieldIDName], + } + + return cgroup, nil +} + +// parseCGroupSubsystems parses procPathCGroup (usually at `/proc/$PID/cgroup`) +// and returns a new map[string]*CGroupSubsys. +func parseCGroupSubsystems(procPathCGroup string) (map[string]*CGroupSubsys, error) { + cgroupFile, err := os.Open(procPathCGroup) + if err != nil { + return nil, err + } + defer cgroupFile.Close() + + scanner := bufio.NewScanner(cgroupFile) + subsystems := make(map[string]*CGroupSubsys) + + for scanner.Scan() { + cgroup, err := NewCGroupSubsysFromLine(scanner.Text()) + if err != nil { + return nil, err + } + for _, subsys := range cgroup.Subsystems { + subsystems[subsys] = cgroup + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return subsystems, nil +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys_test.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys_test.go new file mode 100644 index 00000000000..550374209a1 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/subsys_test.go @@ -0,0 +1,116 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewCGroupSubsysFromLine(t *testing.T) { + testTable := []struct { + name string + line string + expectedSubsys *CGroupSubsys + }{ + { + name: "single-subsys", + line: "1:cpu:/", + expectedSubsys: &CGroupSubsys{ + ID: 1, + Subsystems: []string{"cpu"}, + Name: "/", + }, + }, + { + name: "multi-subsys", + line: "8:cpu,cpuacct,cpuset:/docker/1234567890abcdef", + expectedSubsys: &CGroupSubsys{ + ID: 8, + Subsystems: []string{"cpu", "cpuacct", "cpuset"}, + Name: "/docker/1234567890abcdef", + }, + }, + } + + for _, tt := range testTable { + subsys, err := NewCGroupSubsysFromLine(tt.line) + assert.Equal(t, tt.expectedSubsys, subsys, tt.name) + assert.NoError(t, err, tt.name) + } +} + +func TestNewCGroupSubsysFromLineErr(t *testing.T) { + lines := []string{ + "1:cpu", + "1:cpu,cpuacct:/:/necessary-field", + "not-a-number:cpu:/", + } + _, parseError := strconv.Atoi("not-a-number") + + testTable := []struct { + name string + line string + expectedError error + }{ + { + name: "fewer-fields", + line: lines[0], + expectedError: cgroupSubsysFormatInvalidError{lines[0]}, + }, + { + name: "more-fields", + line: lines[1], + expectedError: cgroupSubsysFormatInvalidError{lines[1]}, + }, + { + name: "illegal-id", + line: lines[2], + expectedError: parseError, + }, + } + + for _, tt := range testTable { + subsys, err := NewCGroupSubsysFromLine(tt.line) + assert.Nil(t, subsys, tt.name) + assert.Equal(t, tt.expectedError, err, tt.name) + } +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_period_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_period_us new file mode 100644 index 00000000000..f7393e847d3 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_period_us @@ -0,0 +1 @@ +100000 diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_quota_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_quota_us new file mode 100644 index 00000000000..26f3b3ddf28 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/cpu/cpu.cfs_quota_us @@ -0,0 +1 @@ +600000 diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/empty/cpu.cfs_quota_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/empty/cpu.cfs_quota_us new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/invalid/cpu.cfs_quota_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/invalid/cpu.cfs_quota_us new file mode 100644 index 00000000000..f43dfb15698 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/invalid/cpu.cfs_quota_us @@ -0,0 +1 @@ +non-an-integer diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined-period/cpu.cfs_quota_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined-period/cpu.cfs_quota_us new file mode 100644 index 00000000000..959e88a89af --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined-period/cpu.cfs_quota_us @@ -0,0 +1 @@ +800000 diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_period_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_period_us new file mode 100644 index 00000000000..f7393e847d3 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_period_us @@ -0,0 +1 @@ +100000 diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_quota_us b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_quota_us new file mode 100644 index 00000000000..3a2e3f4984a --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/cgroups/undefined/cpu.cfs_quota_us @@ -0,0 +1 @@ +-1 diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/cgroup b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/cgroup new file mode 100644 index 00000000000..1724dc83892 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/cgroup @@ -0,0 +1,3 @@ +3:memory:/docker/large +2:cpu,cpuacct:/docker +1:cpuset:/ diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/mountinfo b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/mountinfo new file mode 100644 index 00000000000..e68af08a576 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/cgroups/mountinfo @@ -0,0 +1,8 @@ +1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered +2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 +3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw +4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw +5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 +6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset +7 5 0:6 /docker /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct +8 5 0:7 /docker /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-cgroup/cgroup b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-cgroup/cgroup new file mode 100644 index 00000000000..6d9b22bd764 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-cgroup/cgroup @@ -0,0 +1,2 @@ +1:cpu:/cpu +invalid-line: diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-mountinfo/mountinfo b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-mountinfo/mountinfo new file mode 100644 index 00000000000..3c8dabe4c91 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/invalid-mountinfo/mountinfo @@ -0,0 +1 @@ +1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/cgroup b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/cgroup new file mode 100644 index 00000000000..44519662184 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/cgroup @@ -0,0 +1,2 @@ +1:cpu:/docker +2:cpuacct:/docker diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/mountinfo b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/mountinfo new file mode 100644 index 00000000000..245daae6eb4 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/testdata/proc/untranslatable/mountinfo @@ -0,0 +1,2 @@ +31 23 0:24 / /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime shared:1 - cgroup cgroup rw,cpu +32 23 0:25 /docker/0123456789abcdef /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime shared:2 - cgroup cgroup rw,cpuacct diff --git a/internal/otel_collector/processor/memorylimiter/internal/cgroups/util_test.go b/internal/otel_collector/processor/memorylimiter/internal/cgroups/util_test.go new file mode 100644 index 00000000000..f4461055acb --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/cgroups/util_test.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Keep the the original Uber license. + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build linux + +package cgroups + +import ( + "os" + "path/filepath" +) + +var ( + pwd = mustGetWd() + testDataPath = filepath.Join(pwd, "testdata") + testDataCGroupsPath = filepath.Join(testDataPath, "cgroups") + testDataProcPath = filepath.Join(testDataPath, "proc") +) + +func mustGetWd() string { + pwd, err := os.Getwd() + if err != nil { + panic(err) + } + return pwd +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux.go b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux.go new file mode 100644 index 00000000000..611f4a0fac1 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package iruntime + +import "go.opentelemetry.io/collector/processor/memorylimiter/internal/cgroups" + +// TotalMemory returns total available memory. +// This implementation is meant for linux and uses cgroups to determine available memory. +func TotalMemory() (int64, error) { + cgroups, err := cgroups.NewCGroupsForCurrentProcess() + if err != nil { + return 0, err + } + memoryQuota, defined, err := cgroups.MemoryQuota() + if err != nil || !defined { + return 0, err + } + return memoryQuota, nil +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux_test.go b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux_test.go new file mode 100644 index 00000000000..ead751ae048 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_linux_test.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package iruntime + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTotalMemory(t *testing.T) { + totalMemory, err := TotalMemory() + require.NoError(t, err) + assert.True(t, totalMemory > 0) +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other.go b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other.go new file mode 100644 index 00000000000..304efbf728e --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux + +package iruntime + +import ( + "fmt" +) + +var errTotalMemoryNotAvailable = fmt.Errorf("reading cgroups total memory is available only on linux") + +// TotalMemory returns total available memory. +// This is non-Linux version that returns -1 and errTotalMemoryNotAvailable. +func TotalMemory() (int64, error) { + return -1, errTotalMemoryNotAvailable +} diff --git a/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other_test.go b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other_test.go new file mode 100644 index 00000000000..6ae3a9be712 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/internal/iruntime/total_memory_other_test.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux + +package iruntime + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTotalMemory(t *testing.T) { + totalMemory, err := TotalMemory() + require.Error(t, err) + assert.Equal(t, int64(-1), totalMemory) +} diff --git a/internal/otel_collector/processor/memorylimiter/memorylimiter.go b/internal/otel_collector/processor/memorylimiter/memorylimiter.go new file mode 100644 index 00000000000..5954e27f038 --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/memorylimiter.go @@ -0,0 +1,339 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memorylimiter + +import ( + "context" + "errors" + "fmt" + "runtime" + "sync/atomic" + "time" + + "go.opencensus.io/stats" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/processor/memorylimiter/internal/iruntime" +) + +const ( + mibBytes = 1024 * 1024 +) + +var ( + // errForcedDrop will be returned to callers of ConsumeTraceData to indicate + // that data is being dropped due to high memory usage. + errForcedDrop = errors.New("data dropped due to high memory usage") + + // Construction errors + + errCheckIntervalOutOfRange = errors.New( + "checkInterval must be greater than zero") + + errLimitOutOfRange = errors.New( + "memAllocLimit or memoryLimitPercentage must be greater than zero") + + errMemSpikeLimitOutOfRange = errors.New( + "memSpikeLimit must be smaller than memAllocLimit") + + errPercentageLimitOutOfRange = errors.New( + "memoryLimitPercentage and memorySpikePercentage must be greater than zero and less than or equal to hundred", + ) +) + +// make it overridable by tests +var getMemoryFn = iruntime.TotalMemory + +type memoryLimiter struct { + usageChecker memUsageChecker + + memCheckWait time.Duration + ballastSize uint64 + + // forceDrop is used atomically to indicate when data should be dropped. + forceDrop int64 + + ticker *time.Ticker + + lastGCDone time.Time + + // The function to read the mem values is set as a reference to help with + // testing different values. + readMemStatsFn func(m *runtime.MemStats) + + // Fields used for logging. + procName string + logger *zap.Logger + configMismatchedLogged bool + + obsrep *obsreport.ProcessorObsReport +} + +// Minimum interval between forced GC when in soft limited mode. We don't want to +// do GCs too frequently since it is a CPU-heavy operation. +const minGCIntervalWhenSoftLimited = 10 * time.Second + +// newMemoryLimiter returns a new memorylimiter processor. +func newMemoryLimiter(logger *zap.Logger, cfg *Config) (*memoryLimiter, error) { + ballastSize := uint64(cfg.BallastSizeMiB) * mibBytes + + if cfg.CheckInterval <= 0 { + return nil, errCheckIntervalOutOfRange + } + if cfg.MemoryLimitMiB == 0 && cfg.MemoryLimitPercentage == 0 { + return nil, errLimitOutOfRange + } + + usageChecker, err := getMemUsageChecker(cfg, logger) + if err != nil { + return nil, err + } + + logger.Info("Memory limiter configured", + zap.Uint64("limit_mib", usageChecker.memAllocLimit), + zap.Uint64("spike_limit_mib", usageChecker.memSpikeLimit), + zap.Duration("check_interval", cfg.CheckInterval)) + + ml := &memoryLimiter{ + usageChecker: *usageChecker, + memCheckWait: cfg.CheckInterval, + ballastSize: ballastSize, + ticker: time.NewTicker(cfg.CheckInterval), + readMemStatsFn: runtime.ReadMemStats, + procName: cfg.Name(), + logger: logger, + obsrep: obsreport.NewProcessorObsReport(configtelemetry.GetMetricsLevelFlagValue(), cfg.Name()), + } + + ml.startMonitoring() + + return ml, nil +} + +func getMemUsageChecker(cfg *Config, logger *zap.Logger) (*memUsageChecker, error) { + memAllocLimit := uint64(cfg.MemoryLimitMiB) * mibBytes + memSpikeLimit := uint64(cfg.MemorySpikeLimitMiB) * mibBytes + if cfg.MemoryLimitMiB != 0 { + return newFixedMemUsageChecker(memAllocLimit, memSpikeLimit) + } + totalMemory, err := getMemoryFn() + if err != nil { + return nil, fmt.Errorf("failed to get total memory, use fixed memory settings (limit_mib): %w", err) + } + logger.Info("Using percentage memory limiter", + zap.Int64("total_memory", totalMemory), + zap.Uint32("limit_percentage", cfg.MemoryLimitPercentage), + zap.Uint32("spike_limit_percentage", cfg.MemorySpikePercentage)) + return newPercentageMemUsageChecker(totalMemory, int64(cfg.MemoryLimitPercentage), int64(cfg.MemorySpikePercentage)) +} + +func (ml *memoryLimiter) shutdown(context.Context) error { + ml.ticker.Stop() + return nil +} + +// ProcessTraces implements the TProcessor interface +func (ml *memoryLimiter) ProcessTraces(ctx context.Context, td pdata.Traces) (pdata.Traces, error) { + numSpans := td.SpanCount() + if ml.forcingDrop() { + stats.Record( + ctx, + processor.StatDroppedSpanCount.M(int64(numSpans)), + processor.StatTraceBatchesDroppedCount.M(1)) + + // TODO: actually to be 100% sure that this is "refused" and not "dropped" + // it is necessary to check the pipeline to see if this is directly connected + // to a receiver (ie.: a receiver is on the call stack). For now it + // assumes that the pipeline is properly configured and a receiver is on the + // callstack. + ml.obsrep.TracesRefused(ctx, numSpans) + + return td, errForcedDrop + } + + // Even if the next consumer returns error record the data as accepted by + // this processor. + ml.obsrep.TracesAccepted(ctx, numSpans) + return td, nil +} + +// ProcessMetrics implements the MProcessor interface +func (ml *memoryLimiter) ProcessMetrics(ctx context.Context, md pdata.Metrics) (pdata.Metrics, error) { + _, numDataPoints := md.MetricAndDataPointCount() + if ml.forcingDrop() { + // TODO: actually to be 100% sure that this is "refused" and not "dropped" + // it is necessary to check the pipeline to see if this is directly connected + // to a receiver (ie.: a receiver is on the call stack). For now it + // assumes that the pipeline is properly configured and a receiver is on the + // callstack. + ml.obsrep.MetricsRefused(ctx, numDataPoints) + + return md, errForcedDrop + } + + // Even if the next consumer returns error record the data as accepted by + // this processor. + ml.obsrep.MetricsAccepted(ctx, numDataPoints) + return md, nil +} + +// ProcessLogs implements the LProcessor interface +func (ml *memoryLimiter) ProcessLogs(ctx context.Context, ld pdata.Logs) (pdata.Logs, error) { + numRecords := ld.LogRecordCount() + if ml.forcingDrop() { + // TODO: actually to be 100% sure that this is "refused" and not "dropped" + // it is necessary to check the pipeline to see if this is directly connected + // to a receiver (ie.: a receiver is on the call stack). For now it + // assumes that the pipeline is properly configured and a receiver is on the + // callstack. + ml.obsrep.LogsRefused(ctx, numRecords) + + return ld, errForcedDrop + } + + // Even if the next consumer returns error record the data as accepted by + // this processor. + ml.obsrep.LogsAccepted(ctx, numRecords) + return ld, nil +} + +func (ml *memoryLimiter) readMemStats() *runtime.MemStats { + ms := &runtime.MemStats{} + ml.readMemStatsFn(ms) + // If proper configured ms.Alloc should be at least ml.ballastSize but since + // a misconfiguration is possible check for that here. + if ms.Alloc >= ml.ballastSize { + ms.Alloc -= ml.ballastSize + } else if !ml.configMismatchedLogged { + // This indicates misconfiguration. Log it once. + ml.configMismatchedLogged = true + ml.logger.Warn(typeStr + " is likely incorrectly configured. " + ballastSizeMibKey + + " must be set equal to --mem-ballast-size-mib command line option.") + } + + return ms +} + +// startMonitoring starts a ticker'd goroutine that will check memory usage +// every checkInterval period. +func (ml *memoryLimiter) startMonitoring() { + go func() { + for range ml.ticker.C { + ml.checkMemLimits() + } + }() +} + +// forcingDrop indicates when memory resources need to be released. +func (ml *memoryLimiter) forcingDrop() bool { + return atomic.LoadInt64(&ml.forceDrop) != 0 +} + +func (ml *memoryLimiter) setForcingDrop(b bool) { + var i int64 + if b { + i = 1 + } + atomic.StoreInt64(&ml.forceDrop, i) +} + +func memstatToZapField(ms *runtime.MemStats) zap.Field { + return zap.Uint64("cur_mem_mib", ms.Alloc/1024/1024) +} + +func (ml *memoryLimiter) doGCandReadMemStats() *runtime.MemStats { + runtime.GC() + ml.lastGCDone = time.Now() + ms := ml.readMemStats() + ml.logger.Info("Memory usage after GC.", memstatToZapField(ms)) + return ms +} + +func (ml *memoryLimiter) checkMemLimits() { + ms := ml.readMemStats() + + ml.logger.Debug("Currently used memory.", memstatToZapField(ms)) + + if ml.usageChecker.aboveHardLimit(ms) { + ml.logger.Warn("Memory usage is above hard limit. Forcing a GC.", memstatToZapField(ms)) + ms = ml.doGCandReadMemStats() + } + + // Remember current dropping state. + wasForcingDrop := ml.forcingDrop() + + // Check if the memory usage is above the soft limit. + mustForceDrop := ml.usageChecker.aboveSoftLimit(ms) + + if wasForcingDrop && !mustForceDrop { + // Was previously dropping but enough memory is available now, no need to limit. + ml.logger.Info("Memory usage back within limits. Resuming normal operation.", memstatToZapField(ms)) + } + + if !wasForcingDrop && mustForceDrop { + // We are above soft limit, do a GC if it wasn't done recently and see if + // it brings memory usage below the soft limit. + if time.Since(ml.lastGCDone) > minGCIntervalWhenSoftLimited { + ml.logger.Info("Memory usage is above soft limit. Forcing a GC.", memstatToZapField(ms)) + ms = ml.doGCandReadMemStats() + // Check the limit again to see if GC helped. + mustForceDrop = ml.usageChecker.aboveSoftLimit(ms) + } + + if mustForceDrop { + ml.logger.Warn("Memory usage is above soft limit. Dropping data.", memstatToZapField(ms)) + } + } + + ml.setForcingDrop(mustForceDrop) +} + +type memUsageChecker struct { + memAllocLimit uint64 + memSpikeLimit uint64 +} + +func (d memUsageChecker) aboveSoftLimit(ms *runtime.MemStats) bool { + return ms.Alloc >= d.memAllocLimit-d.memSpikeLimit +} + +func (d memUsageChecker) aboveHardLimit(ms *runtime.MemStats) bool { + return ms.Alloc >= d.memAllocLimit +} + +func newFixedMemUsageChecker(memAllocLimit, memSpikeLimit uint64) (*memUsageChecker, error) { + if memSpikeLimit >= memAllocLimit { + return nil, errMemSpikeLimitOutOfRange + } + if memSpikeLimit == 0 { + // If spike limit is unspecified use 20% of mem limit. + memSpikeLimit = memAllocLimit / 5 + } + return &memUsageChecker{ + memAllocLimit: memAllocLimit, + memSpikeLimit: memSpikeLimit, + }, nil +} + +func newPercentageMemUsageChecker(totalMemory int64, percentageLimit, percentageSpike int64) (*memUsageChecker, error) { + if percentageLimit > 100 || percentageLimit <= 0 || percentageSpike > 100 || percentageSpike <= 0 { + return nil, errPercentageLimitOutOfRange + } + return newFixedMemUsageChecker(uint64(percentageLimit*totalMemory)/100, uint64(percentageSpike*totalMemory)/100) +} diff --git a/internal/otel_collector/processor/memorylimiter/memorylimiter_test.go b/internal/otel_collector/processor/memorylimiter/memorylimiter_test.go new file mode 100644 index 00000000000..396038e5f9d --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/memorylimiter_test.go @@ -0,0 +1,410 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memorylimiter + +import ( + "context" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/processor/memorylimiter/internal/iruntime" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +func TestNew(t *testing.T) { + type args struct { + nextConsumer consumer.TracesConsumer + checkInterval time.Duration + memoryLimitMiB uint32 + memorySpikeLimitMiB uint32 + } + sink := new(consumertest.TracesSink) + tests := []struct { + name string + args args + wantErr error + }{ + { + name: "zero_checkInterval", + args: args{ + nextConsumer: sink, + }, + wantErr: errCheckIntervalOutOfRange, + }, + { + name: "zero_memAllocLimit", + args: args{ + nextConsumer: sink, + checkInterval: 100 * time.Millisecond, + }, + wantErr: errLimitOutOfRange, + }, + { + name: "memSpikeLimit_gt_memAllocLimit", + args: args{ + nextConsumer: sink, + checkInterval: 100 * time.Millisecond, + memoryLimitMiB: 1, + memorySpikeLimitMiB: 2, + }, + wantErr: errMemSpikeLimitOutOfRange, + }, + { + name: "success", + args: args{ + nextConsumer: sink, + checkInterval: 100 * time.Millisecond, + memoryLimitMiB: 1024, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.CheckInterval = tt.args.checkInterval + cfg.MemoryLimitMiB = tt.args.memoryLimitMiB + cfg.MemorySpikeLimitMiB = tt.args.memorySpikeLimitMiB + got, err := newMemoryLimiter(zap.NewNop(), cfg) + if err != tt.wantErr { + t.Errorf("newMemoryLimiter() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil { + assert.NoError(t, got.shutdown(context.Background())) + } + }) + } +} + +// TestMetricsMemoryPressureResponse manipulates results from querying memory and +// check expected side effects. +func TestMetricsMemoryPressureResponse(t *testing.T) { + var currentMemAlloc uint64 + ml := &memoryLimiter{ + usageChecker: memUsageChecker{ + memAllocLimit: 1024, + }, + readMemStatsFn: func(ms *runtime.MemStats) { + ms.Alloc = currentMemAlloc + }, + obsrep: obsreport.NewProcessorObsReport(configtelemetry.LevelNone, ""), + logger: zap.NewNop(), + } + mp, err := processorhelper.NewMetricsProcessor( + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + }, + consumertest.NewMetricsNop(), + ml, + processorhelper.WithCapabilities(processorCapabilities), + processorhelper.WithShutdown(ml.shutdown)) + require.NoError(t, err) + + ctx := context.Background() + md := pdata.NewMetrics() + + // Below memAllocLimit. + currentMemAlloc = 800 + ml.checkMemLimits() + assert.NoError(t, mp.ConsumeMetrics(ctx, md)) + + // Above memAllocLimit. + currentMemAlloc = 1800 + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, mp.ConsumeMetrics(ctx, md)) + + // Check ballast effect + ml.ballastSize = 1000 + + // Below memAllocLimit accounting for ballast. + currentMemAlloc = 800 + ml.ballastSize + ml.checkMemLimits() + assert.NoError(t, mp.ConsumeMetrics(ctx, md)) + + // Above memAllocLimit even accountiing for ballast. + currentMemAlloc = 1800 + ml.ballastSize + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, mp.ConsumeMetrics(ctx, md)) + + // Restore ballast to default. + ml.ballastSize = 0 + + // Check spike limit + ml.usageChecker.memSpikeLimit = 512 + + // Below memSpikeLimit. + currentMemAlloc = 500 + ml.checkMemLimits() + assert.NoError(t, mp.ConsumeMetrics(ctx, md)) + + // Above memSpikeLimit. + currentMemAlloc = 550 + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, mp.ConsumeMetrics(ctx, md)) + +} + +// TestTraceMemoryPressureResponse manipulates results from querying memory and +// check expected side effects. +func TestTraceMemoryPressureResponse(t *testing.T) { + var currentMemAlloc uint64 + ml := &memoryLimiter{ + usageChecker: memUsageChecker{ + memAllocLimit: 1024, + }, + readMemStatsFn: func(ms *runtime.MemStats) { + ms.Alloc = currentMemAlloc + }, + obsrep: obsreport.NewProcessorObsReport(configtelemetry.LevelNone, ""), + logger: zap.NewNop(), + } + tp, err := processorhelper.NewTraceProcessor( + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + }, + consumertest.NewTracesNop(), + ml, + processorhelper.WithCapabilities(processorCapabilities), + processorhelper.WithShutdown(ml.shutdown)) + require.NoError(t, err) + + ctx := context.Background() + td := pdata.NewTraces() + + // Below memAllocLimit. + currentMemAlloc = 800 + ml.checkMemLimits() + assert.NoError(t, tp.ConsumeTraces(ctx, td)) + + // Above memAllocLimit. + currentMemAlloc = 1800 + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, tp.ConsumeTraces(ctx, td)) + + // Check ballast effect + ml.ballastSize = 1000 + + // Below memAllocLimit accounting for ballast. + currentMemAlloc = 800 + ml.ballastSize + ml.checkMemLimits() + assert.NoError(t, tp.ConsumeTraces(ctx, td)) + + // Above memAllocLimit even accountiing for ballast. + currentMemAlloc = 1800 + ml.ballastSize + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, tp.ConsumeTraces(ctx, td)) + + // Restore ballast to default. + ml.ballastSize = 0 + + // Check spike limit + ml.usageChecker.memSpikeLimit = 512 + + // Below memSpikeLimit. + currentMemAlloc = 500 + ml.checkMemLimits() + assert.NoError(t, tp.ConsumeTraces(ctx, td)) + + // Above memSpikeLimit. + currentMemAlloc = 550 + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, tp.ConsumeTraces(ctx, td)) + +} + +// TestLogMemoryPressureResponse manipulates results from querying memory and +// check expected side effects. +func TestLogMemoryPressureResponse(t *testing.T) { + var currentMemAlloc uint64 + ml := &memoryLimiter{ + usageChecker: memUsageChecker{ + memAllocLimit: 1024, + }, + readMemStatsFn: func(ms *runtime.MemStats) { + ms.Alloc = currentMemAlloc + }, + obsrep: obsreport.NewProcessorObsReport(configtelemetry.LevelNone, ""), + logger: zap.NewNop(), + } + lp, err := processorhelper.NewLogsProcessor( + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + }, + consumertest.NewLogsNop(), + ml, + processorhelper.WithCapabilities(processorCapabilities), + processorhelper.WithShutdown(ml.shutdown)) + require.NoError(t, err) + + ctx := context.Background() + ld := pdata.NewLogs() + + // Below memAllocLimit. + currentMemAlloc = 800 + ml.checkMemLimits() + assert.NoError(t, lp.ConsumeLogs(ctx, ld)) + + // Above memAllocLimit. + currentMemAlloc = 1800 + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, lp.ConsumeLogs(ctx, ld)) + + // Check ballast effect + ml.ballastSize = 1000 + + // Below memAllocLimit accounting for ballast. + currentMemAlloc = 800 + ml.ballastSize + ml.checkMemLimits() + assert.NoError(t, lp.ConsumeLogs(ctx, ld)) + + // Above memAllocLimit even accountiing for ballast. + currentMemAlloc = 1800 + ml.ballastSize + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, lp.ConsumeLogs(ctx, ld)) + + // Restore ballast to default. + ml.ballastSize = 0 + + // Check spike limit + ml.usageChecker.memSpikeLimit = 512 + + // Below memSpikeLimit. + currentMemAlloc = 500 + ml.checkMemLimits() + assert.NoError(t, lp.ConsumeLogs(ctx, ld)) + + // Above memSpikeLimit. + currentMemAlloc = 550 + ml.checkMemLimits() + assert.Equal(t, errForcedDrop, lp.ConsumeLogs(ctx, ld)) +} + +func TestGetDecision(t *testing.T) { + t.Run("fixed_limit", func(t *testing.T) { + d, err := getMemUsageChecker(&Config{MemoryLimitMiB: 100, MemorySpikeLimitMiB: 20}, zap.NewNop()) + require.NoError(t, err) + assert.Equal(t, &memUsageChecker{ + memAllocLimit: 100 * mibBytes, + memSpikeLimit: 20 * mibBytes, + }, d) + }) + t.Run("fixed_limit_error", func(t *testing.T) { + d, err := getMemUsageChecker(&Config{MemoryLimitMiB: 20, MemorySpikeLimitMiB: 100}, zap.NewNop()) + require.Error(t, err) + assert.Nil(t, d) + }) + + t.Cleanup(func() { + getMemoryFn = iruntime.TotalMemory + }) + getMemoryFn = func() (int64, error) { + return 100 * mibBytes, nil + } + t.Run("percentage_limit", func(t *testing.T) { + d, err := getMemUsageChecker(&Config{MemoryLimitPercentage: 50, MemorySpikePercentage: 10}, zap.NewNop()) + require.NoError(t, err) + assert.Equal(t, &memUsageChecker{ + memAllocLimit: 50 * mibBytes, + memSpikeLimit: 10 * mibBytes, + }, d) + }) + t.Run("percentage_limit_error", func(t *testing.T) { + d, err := getMemUsageChecker(&Config{MemoryLimitPercentage: 101, MemorySpikePercentage: 10}, zap.NewNop()) + require.Error(t, err) + assert.Nil(t, d) + d, err = getMemUsageChecker(&Config{MemoryLimitPercentage: 99, MemorySpikePercentage: 101}, zap.NewNop()) + require.Error(t, err) + assert.Nil(t, d) + }) +} + +func TestDropDecision(t *testing.T) { + decison1000Limit30Spike30, err := newPercentageMemUsageChecker(1000, 60, 30) + require.NoError(t, err) + decison1000Limit60Spike50, err := newPercentageMemUsageChecker(1000, 60, 50) + require.NoError(t, err) + decison1000Limit40Spike20, err := newPercentageMemUsageChecker(1000, 40, 20) + require.NoError(t, err) + decison1000Limit40Spike60, err := newPercentageMemUsageChecker(1000, 40, 60) + require.Error(t, err) + assert.Nil(t, decison1000Limit40Spike60) + + tests := []struct { + name string + usageChecker memUsageChecker + ms *runtime.MemStats + shouldDrop bool + }{ + { + name: "should drop over limit", + usageChecker: *decison1000Limit30Spike30, + ms: &runtime.MemStats{Alloc: 600}, + shouldDrop: true, + }, + { + name: "should not drop", + usageChecker: *decison1000Limit30Spike30, + ms: &runtime.MemStats{Alloc: 100}, + shouldDrop: false, + }, + { + name: "should not drop spike, fixed usageChecker", + usageChecker: memUsageChecker{ + memAllocLimit: 600, + memSpikeLimit: 500, + }, + ms: &runtime.MemStats{Alloc: 300}, + shouldDrop: true, + }, + { + name: "should drop, spike, percentage usageChecker", + usageChecker: *decison1000Limit60Spike50, + ms: &runtime.MemStats{Alloc: 300}, + shouldDrop: true, + }, + { + name: "should drop, spike, percentage usageChecker", + usageChecker: *decison1000Limit40Spike20, + ms: &runtime.MemStats{Alloc: 250}, + shouldDrop: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + shouldDrop := test.usageChecker.aboveSoftLimit(test.ms) + assert.Equal(t, test.shouldDrop, shouldDrop) + }) + } +} diff --git a/internal/otel_collector/processor/memorylimiter/testdata/config.yaml b/internal/otel_collector/processor/memorylimiter/testdata/config.yaml new file mode 100644 index 00000000000..5f6504ba93c --- /dev/null +++ b/internal/otel_collector/processor/memorylimiter/testdata/config.yaml @@ -0,0 +1,36 @@ +receivers: + examplereceiver: + +processors: + memory_limiter: + # empty config + + memory_limiter/with-settings: + # check_interval is the time between measurements of memory usage for the + # purposes of avoiding going over the limits. Defaults to zero, so no + # checks will be performed. Values below 1 second are not recommended since + # it can result in unnecessary CPU consumption. + check_interval: 5s + + # Maximum amount of memory, in MiB, targeted to be allocated by the process heap. + # Note that typically the total memory usage of process will be about 50MiB higher + # than this value. + limit_mib: 4000 + + # The maximum, in MiB, spike expected between the measurements of memory usage. + spike_limit_mib: 500 + + # BallastSizeMiB is the size, in MiB, of the ballast size being used by the process. + # This must match the value of mem-ballast-size-mib command line option (if used) + # otherwise the memory limiter will not work correctly. + ballast_size_mib: 2000 + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [memory_limiter/with-settings] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/metrics.go b/internal/otel_collector/processor/metrics.go new file mode 100644 index 00000000000..faf78477544 --- /dev/null +++ b/internal/otel_collector/processor/metrics.go @@ -0,0 +1,159 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processor + +import ( + "context" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/translator/conventions" +) + +// Keys and stats for telemetry. +var ( + TagServiceNameKey, _ = tag.NewKey("service") + TagProcessorNameKey, _ = tag.NewKey(obsreport.ProcessorKey) + + StatReceivedSpanCount = stats.Int64( + "spans_received", + "counts the number of spans received", + stats.UnitDimensionless) + StatDroppedSpanCount = stats.Int64( + "spans_dropped", + "counts the number of spans dropped", + stats.UnitDimensionless) + + StatTraceBatchesDroppedCount = stats.Int64( + "trace_batches_dropped", + "counts the number of trace batches dropped", + stats.UnitDimensionless) +) + +// SpanCountStats represents span count stats grouped by service if DETAILED telemetry level is set, +// otherwise only overall span count is stored in serviceSpansCounts. +type SpanCountStats struct { + serviceSpansCounts map[string]int + allSpansCount int + isDetailed bool +} + +func NewSpanCountStats(td pdata.Traces) *SpanCountStats { + scm := &SpanCountStats{ + allSpansCount: td.SpanCount(), + } + if serviceTagsEnabled() { + scm.serviceSpansCounts = spanCountByResourceStringAttribute(td, conventions.AttributeServiceName) + scm.isDetailed = true + } + return scm +} + +func (scm *SpanCountStats) GetAllSpansCount() int { + return scm.allSpansCount +} + +// MetricTagKeys returns the metric tag keys according to the given telemetry level. +func MetricTagKeys() []tag.Key { + return []tag.Key{ + TagProcessorNameKey, + TagServiceNameKey, + } +} + +// MetricViews return the metrics views according to given telemetry level. +func MetricViews() []*view.View { + tagKeys := MetricTagKeys() + // There are some metrics enabled, return the views. + receivedBatchesView := &view.View{ + Name: "batches_received", + Measure: StatReceivedSpanCount, + Description: "The number of span batches received.", + TagKeys: tagKeys, + Aggregation: view.Count(), + } + droppedBatchesView := &view.View{ + Measure: StatTraceBatchesDroppedCount, + Description: "The number of span batches dropped.", + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + receivedSpansView := &view.View{ + Name: StatReceivedSpanCount.Name(), + Measure: StatReceivedSpanCount, + Description: "The number of spans received.", + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + droppedSpansView := &view.View{ + Name: StatDroppedSpanCount.Name(), + Measure: StatDroppedSpanCount, + Description: "The number of spans dropped.", + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + + legacyViews := []*view.View{ + receivedBatchesView, + droppedBatchesView, + receivedSpansView, + droppedSpansView, + } + + return obsreport.ProcessorMetricViews("", legacyViews) +} + +// RecordsSpanCountMetrics reports span count metrics for specified measure. +func RecordsSpanCountMetrics(ctx context.Context, scm *SpanCountStats, measure *stats.Int64Measure) { + if scm.isDetailed { + for serviceName, spanCount := range scm.serviceSpansCounts { + statsTags := []tag.Mutator{tag.Insert(TagServiceNameKey, serviceName)} + _ = stats.RecordWithTags(ctx, statsTags, measure.M(int64(spanCount))) + } + return + } + + stats.Record(ctx, measure.M(int64(scm.allSpansCount))) +} + +func serviceTagsEnabled() bool { + level := configtelemetry.GetMetricsLevelFlagValue() + return level == configtelemetry.LevelDetailed +} + +// spanCountByResourceStringAttribute calculates the number of spans by resource specified by +// provided string attribute attrKey. +func spanCountByResourceStringAttribute(td pdata.Traces, attrKey string) map[string]int { + spanCounts := make(map[string]int) + + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + var attrStringVal string + if attrVal, ok := rs.Resource().Attributes().Get(attrKey); ok { + attrStringVal = attrVal.StringVal() + } + ilss := rs.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + spanCounts[attrStringVal] += ilss.At(j).Spans().Len() + } + } + return spanCounts +} diff --git a/internal/otel_collector/processor/metrics_test.go b/internal/otel_collector/processor/metrics_test.go new file mode 100644 index 00000000000..a3c45b677cf --- /dev/null +++ b/internal/otel_collector/processor/metrics_test.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processor + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestSpanCountByResourceStringAttribute(t *testing.T) { + td := testdata.GenerateTraceDataEmpty() + require.EqualValues(t, 0, len(spanCountByResourceStringAttribute(td, "resource-attr"))) + + td = testdata.GenerateTraceDataOneSpan() + spanCounts := spanCountByResourceStringAttribute(td, "resource-attr") + require.EqualValues(t, 1, len(spanCounts)) + require.EqualValues(t, 1, spanCounts["resource-attr-val-1"]) + + td = testdata.GenerateTraceDataTwoSpansSameResource() + spanCounts = spanCountByResourceStringAttribute(td, "resource-attr") + require.EqualValues(t, 1, len(spanCounts)) + require.EqualValues(t, 2, spanCounts["resource-attr-val-1"]) + + td = testdata.GenerateTraceDataTwoSpansSameResourceOneDifferent() + spanCounts = spanCountByResourceStringAttribute(td, "resource-attr") + require.EqualValues(t, 2, len(spanCounts)) + require.EqualValues(t, 2, spanCounts["resource-attr-val-1"]) + require.EqualValues(t, 1, spanCounts["resource-attr-val-2"]) +} diff --git a/internal/otel_collector/processor/processorhelper/attraction.go b/internal/otel_collector/processor/processorhelper/attraction.go new file mode 100644 index 00000000000..87961240dc5 --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/attraction.go @@ -0,0 +1,283 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + "fmt" + "regexp" + "strings" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterhelper" +) + +// Settings +type Settings struct { + // Actions specifies the list of attributes to act on. + // The set of actions are {INSERT, UPDATE, UPSERT, DELETE, HASH, EXTRACT}. + // This is a required field. + Actions []ActionKeyValue `mapstructure:"actions"` +} + +// ActionKeyValue specifies the attribute key to act upon. +type ActionKeyValue struct { + // Key specifies the attribute to act upon. + // This is a required field. + Key string `mapstructure:"key"` + + // Value specifies the value to populate for the key. + // The type of the value is inferred from the configuration. + Value interface{} `mapstructure:"value"` + + // A regex pattern must be specified for the action EXTRACT. + // It uses the attribute specified by `key' to extract values from + // The target keys are inferred based on the names of the matcher groups + // provided and the names will be inferred based on the values of the + // matcher group. + // Note: All subexpressions must have a name. + // Note: The value type of the source key must be a string. If it isn't, + // no extraction will occur. + RegexPattern string `mapstructure:"pattern"` + + // FromAttribute specifies the attribute to use to populate + // the value. If the attribute doesn't exist, no action is performed. + FromAttribute string `mapstructure:"from_attribute"` + + // Action specifies the type of action to perform. + // The set of values are {INSERT, UPDATE, UPSERT, DELETE, HASH}. + // Both lower case and upper case are supported. + // INSERT - Inserts the key/value to attributes when the key does not exist. + // No action is applied to attributes where the key already exists. + // Either Value or FromAttribute must be set. + // UPDATE - Updates an existing key with a value. No action is applied + // to attributes where the key does not exist. + // Either Value or FromAttribute must be set. + // UPSERT - Performs insert or update action depending on the attributes + // containing the key. The key/value is insert to attributes + // that did not originally have the key. The key/value is updated + // for attributes where the key already existed. + // Either Value or FromAttribute must be set. + // DELETE - Deletes the attribute. If the key doesn't exist, + // no action is performed. + // HASH - Calculates the SHA-1 hash of an existing value and overwrites the + // value with it's SHA-1 hash result. + // EXTRACT - Extracts values using a regular expression rule from the input + // 'key' to target keys specified in the 'rule'. If a target key + // already exists, it will be overridden. + // This is a required field. + Action Action `mapstructure:"action"` +} + +// Action is the enum to capture the four types of actions to perform on an +// attribute. +type Action string + +const ( + // INSERT adds the key/value to attributes when the key does not exist. + // No action is applied to attributes where the key already exists. + INSERT Action = "insert" + + // UPDATE updates an existing key with a value. No action is applied + // to attributes where the key does not exist. + UPDATE Action = "update" + + // UPSERT performs the INSERT or UPDATE action. The key/value is + // insert to attributes that did not originally have the key. The key/value is + // updated for attributes where the key already existed. + UPSERT Action = "upsert" + + // DELETE deletes the attribute. If the key doesn't exist, no action is performed. + DELETE Action = "delete" + + // HASH calculates the SHA-1 hash of an existing value and overwrites the + // value with it's SHA-1 hash result. + HASH Action = "hash" + + // EXTRACT extracts values using a regular expression rule from the input + // 'key' to target keys specified in the 'rule'. If a target key already + // exists, it will be overridden. + EXTRACT Action = "extract" +) + +type attributeAction struct { + Key string + FromAttribute string + // Compiled regex if provided + Regex *regexp.Regexp + // Attribute names extracted from the regexp's subexpressions. + AttrNames []string + // Number of non empty strings in above array + + // TODO https://go.opentelemetry.io/collector/issues/296 + // Do benchmark testing between having action be of type string vs integer. + // The reason is attributes processor will most likely be commonly used + // and could impact performance. + Action Action + AttributeValue *pdata.AttributeValue +} + +type AttrProc struct { + actions []attributeAction +} + +// NewAttrProc validates that the input configuration has all of the required fields for the processor +// and returns a AttrProc to be used to process attributes. +// An error is returned if there are any invalid inputs. +func NewAttrProc(settings *Settings) (*AttrProc, error) { + var attributeActions []attributeAction + for i, a := range settings.Actions { + // `key` is a required field + if a.Key == "" { + return nil, fmt.Errorf("error creating AttrProc due to missing required field \"key\" at the %d-th actions", i) + } + + // Convert `action` to lowercase for comparison. + a.Action = Action(strings.ToLower(string(a.Action))) + action := attributeAction{ + Key: a.Key, + Action: a.Action, + } + + switch a.Action { + case INSERT, UPDATE, UPSERT: + if a.Value == nil && a.FromAttribute == "" { + return nil, fmt.Errorf("error creating AttrProc. Either field \"value\" or \"from_attribute\" setting must be specified for %d-th action", i) + } + + if a.Value != nil && a.FromAttribute != "" { + return nil, fmt.Errorf("error creating AttrProc due to both fields \"value\" and \"from_attribute\" being set at the %d-th actions", i) + } + if a.RegexPattern != "" { + return nil, fmt.Errorf("error creating AttrProc. Action \"%s\" does not use the \"pattern\" field. This must not be specified for %d-th action", a.Action, i) + + } + // Convert the raw value from the configuration to the internal trace representation of the value. + if a.Value != nil { + val, err := filterhelper.NewAttributeValueRaw(a.Value) + if err != nil { + return nil, err + } + action.AttributeValue = &val + } else { + action.FromAttribute = a.FromAttribute + } + case HASH, DELETE: + if a.Value != nil || a.FromAttribute != "" || a.RegexPattern != "" { + return nil, fmt.Errorf("error creating AttrProc. Action \"%s\" does not use \"value\", \"pattern\" or \"from_attribute\" field. These must not be specified for %d-th action", a.Action, i) + } + case EXTRACT: + if a.Value != nil || a.FromAttribute != "" { + return nil, fmt.Errorf("error creating AttrProc. Action \"%s\" does not use \"value\" or \"from_attribute\" field. These must not be specified for %d-th action", a.Action, i) + } + if a.RegexPattern == "" { + return nil, fmt.Errorf("error creating AttrProc due to missing required field \"pattern\" for action \"%s\" at the %d-th action", a.Action, i) + + } + re, err := regexp.Compile(a.RegexPattern) + if err != nil { + return nil, fmt.Errorf("error creating AttrProc. Field \"pattern\" has invalid pattern: \"%s\" to be set at the %d-th actions", a.RegexPattern, i) + } + attrNames := re.SubexpNames() + if len(attrNames) <= 1 { + return nil, fmt.Errorf("error creating AttrProc. Field \"pattern\" contains no named matcher groups at the %d-th actions", i) + } + + for subExpIndex := 1; subExpIndex < len(attrNames); subExpIndex++ { + if attrNames[subExpIndex] == "" { + return nil, fmt.Errorf("error creating AttrProc. Field \"pattern\" contains at least one unnamed matcher group at the %d-th actions", i) + } + } + action.Regex = re + action.AttrNames = attrNames + default: + return nil, fmt.Errorf("error creating AttrProc due to unsupported action %q at the %d-th actions", a.Action, i) + } + + attributeActions = append(attributeActions, action) + } + return &AttrProc{actions: attributeActions}, nil +} + +func (ap *AttrProc) Process(attrs pdata.AttributeMap) { + for _, action := range ap.actions { + // TODO https://go.opentelemetry.io/collector/issues/296 + // Do benchmark testing between having action be of type string vs integer. + // The reason is attributes processor will most likely be commonly used + // and could impact performance. + switch action.Action { + case DELETE: + attrs.Delete(action.Key) + case INSERT: + av, found := getSourceAttributeValue(action, attrs) + if !found { + continue + } + attrs.Insert(action.Key, av) + case UPDATE: + av, found := getSourceAttributeValue(action, attrs) + if !found { + continue + } + attrs.Update(action.Key, av) + case UPSERT: + av, found := getSourceAttributeValue(action, attrs) + if !found { + continue + } + attrs.Upsert(action.Key, av) + case HASH: + hashAttribute(action, attrs) + case EXTRACT: + extractAttributes(action, attrs) + } + } +} + +func getSourceAttributeValue(action attributeAction, attrs pdata.AttributeMap) (pdata.AttributeValue, bool) { + // Set the key with a value from the configuration. + if action.AttributeValue != nil { + return *action.AttributeValue, true + } + + return attrs.Get(action.FromAttribute) +} + +func hashAttribute(action attributeAction, attrs pdata.AttributeMap) { + if value, exists := attrs.Get(action.Key); exists { + sha1Hasher(value) + } +} + +func extractAttributes(action attributeAction, attrs pdata.AttributeMap) { + value, found := attrs.Get(action.Key) + + // Extracting values only functions on strings. + if !found || value.Type() != pdata.AttributeValueSTRING { + return + } + + // Note: The number of matches will always be equal to number of + // subexpressions. + matches := action.Regex.FindStringSubmatch(value.StringVal()) + if matches == nil { + return + } + + // Start from index 1, which is the first submatch (index 0 is the entire + // match). + for i := 1; i < len(matches); i++ { + attrs.UpsertString(action.AttrNames[i], matches[i]) + } +} diff --git a/internal/otel_collector/processor/processorhelper/attraction_test.go b/internal/otel_collector/processor/processorhelper/attraction_test.go new file mode 100644 index 00000000000..50c181b7084 --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/attraction_test.go @@ -0,0 +1,881 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + "crypto/sha1" // #nosec + "encoding/binary" + "errors" + "fmt" + "math" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Common structure for all the Tests +type testCase struct { + name string + inputAttributes map[string]pdata.AttributeValue + expectedAttributes map[string]pdata.AttributeValue +} + +// runIndividualTestCase is the common logic of passing trace data through a configured attributes processor. +func runIndividualTestCase(t *testing.T, tt testCase, ap *AttrProc) { + t.Run(tt.name, func(t *testing.T) { + attrMap := pdata.NewAttributeMap().InitFromMap(tt.inputAttributes) + ap.Process(attrMap) + attrMap.Sort() + require.Equal(t, pdata.NewAttributeMap().InitFromMap(tt.expectedAttributes).Sort(), attrMap) + }) +} + +func TestAttributes_InsertValue(t *testing.T) { + testCases := []testCase{ + // Ensure `attribute1` is set for spans with no attributes. + { + name: "InsertEmptyAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + // Ensure `attribute1` is set. + { + name: "InsertKeyNoExists", + inputAttributes: map[string]pdata.AttributeValue{ + "anotherkey": pdata.NewAttributeValueString("bob"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "anotherkey": pdata.NewAttributeValueString("bob"), + "attribute1": pdata.NewAttributeValueInt(123), + }, + }, + // Ensures no insert is performed because the keys `attribute1` already exists. + { + name: "InsertKeyExists", + inputAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueString("bob"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "attribute1": pdata.NewAttributeValueString("bob"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "attribute1", Action: INSERT, Value: 123}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_InsertFromAttribute(t *testing.T) { + + testCases := []testCase{ + // Ensure no attribute is inserted because because attributes do not exist. + { + name: "InsertEmptyAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure no attribute is inserted because because from_attribute `string_key` does not exist. + { + name: "InsertMissingFromAttribute", + inputAttributes: map[string]pdata.AttributeValue{ + "bob": pdata.NewAttributeValueInt(1), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "bob": pdata.NewAttributeValueInt(1), + }, + }, + // Ensure `string key` is set. + { + name: "InsertAttributeExists", + inputAttributes: map[string]pdata.AttributeValue{ + "anotherkey": pdata.NewAttributeValueInt(8892342), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "anotherkey": pdata.NewAttributeValueInt(8892342), + "string key": pdata.NewAttributeValueInt(8892342), + }, + }, + // Ensures no insert is performed because the keys `string key` already exist. + { + name: "InsertKeysExists", + inputAttributes: map[string]pdata.AttributeValue{ + "anotherkey": pdata.NewAttributeValueInt(8892342), + "string key": pdata.NewAttributeValueString("here"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "anotherkey": pdata.NewAttributeValueInt(8892342), + "string key": pdata.NewAttributeValueString("here"), + }, + }, + } + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "string key", Action: INSERT, FromAttribute: "anotherkey"}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_UpdateValue(t *testing.T) { + + testCases := []testCase{ + // Ensure no changes to the span as there is no attributes map. + { + name: "UpdateNoAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure no changes to the span as the key does not exist. + { + name: "UpdateKeyNoExist", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("foo"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("foo"), + }, + }, + // Ensure the attribute `db.secret` is updated. + { + name: "UpdateAttributes", + inputAttributes: map[string]pdata.AttributeValue{ + "db.secret": pdata.NewAttributeValueString("password1234"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "db.secret": pdata.NewAttributeValueString("redacted"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "db.secret", Action: UPDATE, Value: "redacted"}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_UpdateFromAttribute(t *testing.T) { + + testCases := []testCase{ + // Ensure no changes to the span as there is no attributes map. + { + name: "UpdateNoAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure the attribute `boo` isn't updated because attribute `foo` isn't present in the span. + { + name: "UpdateKeyNoExistFromAttribute", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("bob"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("bob"), + }, + }, + // Ensure no updates as the target key `boo` doesn't exists. + { + name: "UpdateKeyNoExistMainAttributed", + inputAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("over there"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("over there"), + }, + }, + // Ensure no updates as the target key `boo` doesn't exists. + { + name: "UpdateKeyFromExistingAttribute", + inputAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("there is a party over here"), + "boo": pdata.NewAttributeValueString("not here"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("there is a party over here"), + "boo": pdata.NewAttributeValueString("there is a party over here"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "boo", Action: UPDATE, FromAttribute: "foo"}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_UpsertValue(t *testing.T) { + testCases := []testCase{ + // Ensure `region` is set for spans with no attributes. + { + name: "UpsertNoAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{ + "region": pdata.NewAttributeValueString("planet-earth"), + }, + }, + // Ensure `region` is inserted for spans with some attributes(the key doesn't exist). + { + name: "UpsertAttributeNoExist", + inputAttributes: map[string]pdata.AttributeValue{ + "mission": pdata.NewAttributeValueString("to mars"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "mission": pdata.NewAttributeValueString("to mars"), + "region": pdata.NewAttributeValueString("planet-earth"), + }, + }, + // Ensure `region` is updated for spans with the attribute key `region`. + { + name: "UpsertAttributeExists", + inputAttributes: map[string]pdata.AttributeValue{ + "mission": pdata.NewAttributeValueString("to mars"), + "region": pdata.NewAttributeValueString("solar system"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "mission": pdata.NewAttributeValueString("to mars"), + "region": pdata.NewAttributeValueString("planet-earth"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "region", Action: UPSERT, Value: "planet-earth"}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_Extract(t *testing.T) { + testCases := []testCase{ + // Ensure `new_user_key` is not set for spans with no attributes. + { + name: "UpsertEmptyAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure `new_user_key` is not inserted for spans with missing attribute `user_key`. + { + name: "No extract with no target key", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + }, + // Ensure `new_user_key` is not inserted for spans with missing attribute `user_key`. + { + name: "No extract with non string target key", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + "user_key": pdata.NewAttributeValueInt(1234), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + "user_key": pdata.NewAttributeValueInt(1234), + }, + }, + // Ensure `new_user_key` is not updated for spans with attribute + // `user_key` because `user_key` does not match the regular expression. + { + name: "No extract with no pattern matching", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("does not match"), + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("does not match"), + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + }, + // Ensure `new_user_key` is not updated for spans with attribute + // `user_key` because `user_key` does not match all of the regular + // expression. + { + name: "No extract with no pattern matching", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update"), + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update"), + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + }, + // Ensure `new_user_key` and `version` is inserted for spans with attribute `user_key`. + { + name: "Extract insert new values.", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"), + "new_user_key": pdata.NewAttributeValueString("12345678"), + "version": pdata.NewAttributeValueString("v1"), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + }, + // Ensure `new_user_key` and `version` is updated for spans with attribute `user_key`. + { + name: "Extract updates existing values ", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"), + "new_user_key": pdata.NewAttributeValueString("2321"), + "version": pdata.NewAttributeValueString("na"), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"), + "new_user_key": pdata.NewAttributeValueString("12345678"), + "version": pdata.NewAttributeValueString("v1"), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + }, + // Ensure `new_user_key` is updated and `version` is inserted for spans with attribute `user_key`. + { + name: "Extract upserts values", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"), + "new_user_key": pdata.NewAttributeValueString("2321"), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"), + "new_user_key": pdata.NewAttributeValueString("12345678"), + "version": pdata.NewAttributeValueString("v1"), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + + {Key: "user_key", RegexPattern: "^\\/api\\/v1\\/document\\/(?P.*)\\/update\\/(?P.*)$", Action: EXTRACT}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_UpsertFromAttribute(t *testing.T) { + + testCases := []testCase{ + // Ensure `new_user_key` is not set for spans with no attributes. + { + name: "UpsertEmptyAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure `new_user_key` is not inserted for spans with missing attribute `user_key`. + { + name: "UpsertFromAttributeNoExist", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + }, + // Ensure `new_user_key` is inserted for spans with attribute `user_key`. + { + name: "UpsertFromAttributeExistsInsert", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueInt(2245), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueInt(2245), + "new_user_key": pdata.NewAttributeValueInt(2245), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + }, + // Ensure `new_user_key` is updated for spans with attribute `user_key`. + { + name: "UpsertFromAttributeExistsUpdate", + inputAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueInt(2245), + "new_user_key": pdata.NewAttributeValueInt(5422), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "user_key": pdata.NewAttributeValueInt(2245), + "new_user_key": pdata.NewAttributeValueInt(2245), + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "new_user_key", Action: UPSERT, FromAttribute: "user_key"}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_Delete(t *testing.T) { + testCases := []testCase{ + // Ensure the span contains no changes. + { + name: "DeleteEmptyAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure the span contains no changes because the key doesn't exist. + { + name: "DeleteAttributeNoExist", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + }, + // Ensure `duplicate_key` is deleted for spans with the attribute set. + { + name: "DeleteAttributeExists", + inputAttributes: map[string]pdata.AttributeValue{ + "duplicate_key": pdata.NewAttributeValueDouble(3245.6), + "original_key": pdata.NewAttributeValueDouble(3245.6), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "original_key": pdata.NewAttributeValueDouble(3245.6), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "duplicate_key", Action: DELETE}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_HashValue(t *testing.T) { + + intVal := int64(24) + intBytes := make([]byte, int64ByteSize) + binary.LittleEndian.PutUint64(intBytes, uint64(intVal)) + + doubleVal := 2.4 + doubleBytes := make([]byte, float64ByteSize) + binary.LittleEndian.PutUint64(doubleBytes, math.Float64bits(doubleVal)) + + testCases := []testCase{ + // Ensure no changes to the span as there is no attributes map. + { + name: "HashNoAttributes", + inputAttributes: map[string]pdata.AttributeValue{}, + expectedAttributes: map[string]pdata.AttributeValue{}, + }, + // Ensure no changes to the span as the key does not exist. + { + name: "HashKeyNoExist", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("foo"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("foo"), + }, + }, + // Ensure string data types are hashed correctly + { + name: "HashString", + inputAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueString("foo"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueString(sha1Hash([]byte("foo"))), + }, + }, + // Ensure int data types are hashed correctly + { + name: "HashInt", + inputAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueInt(intVal), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueString(sha1Hash(intBytes)), + }, + }, + // Ensure double data types are hashed correctly + { + name: "HashDouble", + inputAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueDouble(doubleVal), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueString(sha1Hash(doubleBytes)), + }, + }, + // Ensure bool data types are hashed correctly + { + name: "HashBoolTrue", + inputAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueBool(true), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueString(sha1Hash([]byte{1})), + }, + }, + // Ensure bool data types are hashed correctly + { + name: "HashBoolFalse", + inputAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueBool(false), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "updateme": pdata.NewAttributeValueString(sha1Hash([]byte{0})), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "updateme", Action: HASH}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestAttributes_FromAttributeNoChange(t *testing.T) { + tc := testCase{ + name: "FromAttributeNoChange", + inputAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "boo": pdata.NewAttributeValueString("ghosts are scary"), + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "boo", Action: INSERT, FromAttribute: "boo"}, + {Key: "boo", Action: UPDATE, FromAttribute: "boo"}, + {Key: "boo", Action: UPSERT, FromAttribute: "boo"}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + runIndividualTestCase(t, tc, ap) +} + +func TestAttributes_Ordering(t *testing.T) { + testCases := []testCase{ + // For this example, the operations performed are + // 1. insert `operation`: `default` + // 2. insert `svc.operation`: `default` + // 3. delete `operation`. + { + name: "OrderingApplyAllSteps", + inputAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "svc.operation": pdata.NewAttributeValueString("default"), + }, + }, + // For this example, the operations performed are + // 1. do nothing for the first action of insert `operation`: `default` + // 2. insert `svc.operation`: `arithmetic` + // 3. delete `operation`. + { + name: "OrderingOperationExists", + inputAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "operation": pdata.NewAttributeValueString("arithmetic"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "svc.operation": pdata.NewAttributeValueString("arithmetic"), + }, + }, + + // For this example, the operations performed are + // 1. insert `operation`: `default` + // 2. update `svc.operation` to `default` + // 3. delete `operation`. + { + name: "OrderingSvcOperationExists", + inputAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "svc.operation": pdata.NewAttributeValueString("some value"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "svc.operation": pdata.NewAttributeValueString("default"), + }, + }, + + // For this example, the operations performed are + // 1. do nothing for the first action of insert `operation`: `default` + // 2. update `svc.operation` to `arithmetic` + // 3. delete `operation`. + { + name: "OrderingBothAttributesExist", + inputAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "operation": pdata.NewAttributeValueString("arithmetic"), + "svc.operation": pdata.NewAttributeValueString("add"), + }, + expectedAttributes: map[string]pdata.AttributeValue{ + "foo": pdata.NewAttributeValueString("casper the friendly ghost"), + "svc.operation": pdata.NewAttributeValueString("arithmetic"), + }, + }, + } + + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "operation", Action: INSERT, Value: "default"}, + {Key: "svc.operation", Action: UPSERT, FromAttribute: "operation"}, + {Key: "operation", Action: DELETE}, + }, + } + + ap, err := NewAttrProc(cfg) + require.Nil(t, err) + require.NotNil(t, ap) + + for _, tt := range testCases { + runIndividualTestCase(t, tt, ap) + } +} + +func TestInvalidConfig(t *testing.T) { + testcase := []struct { + name string + actionLists []ActionKeyValue + errorString string + }{ + { + name: "missing key", + actionLists: []ActionKeyValue{ + {Key: "one", Action: DELETE}, + {Key: "", Value: 123, Action: UPSERT}, + }, + errorString: "error creating AttrProc due to missing required field \"key\" at the 1-th actions", + }, + { + name: "invalid action", + actionLists: []ActionKeyValue{ + {Key: "invalid", Action: "invalid"}, + }, + errorString: "error creating AttrProc due to unsupported action \"invalid\" at the 0-th actions", + }, + { + name: "unsupported value", + actionLists: []ActionKeyValue{ + {Key: "UnsupportedValue", Value: []int{}, Action: UPSERT}, + }, + errorString: "error unsupported value type \"[]int\"", + }, + { + name: "missing value or from attribute", + actionLists: []ActionKeyValue{ + {Key: "MissingValueFromAttributes", Action: INSERT}, + }, + errorString: "error creating AttrProc. Either field \"value\" or \"from_attribute\" setting must be specified for 0-th action", + }, + { + name: "both set value and from attribute", + actionLists: []ActionKeyValue{ + {Key: "BothSet", Value: 123, FromAttribute: "aa", Action: UPSERT}, + }, + errorString: "error creating AttrProc due to both fields \"value\" and \"from_attribute\" being set at the 0-th actions", + }, + { + name: "pattern shouldn't be specified", + actionLists: []ActionKeyValue{ + {Key: "key", RegexPattern: "(?P.*?)$", FromAttribute: "aa", Action: INSERT}, + }, + errorString: "error creating AttrProc. Action \"insert\" does not use the \"pattern\" field. This must not be specified for 0-th action", + }, + { + name: "missing rule for extract", + actionLists: []ActionKeyValue{ + {Key: "aa", Action: EXTRACT}, + }, + errorString: "error creating AttrProc due to missing required field \"pattern\" for action \"extract\" at the 0-th action", + }, + {name: "set value for extract", + actionLists: []ActionKeyValue{ + {Key: "Key", RegexPattern: "(?P.*?)$", Value: "value", Action: EXTRACT}, + }, + errorString: "error creating AttrProc. Action \"extract\" does not use \"value\" or \"from_attribute\" field. These must not be specified for 0-th action", + }, + { + name: "set from attribute for extract", + actionLists: []ActionKeyValue{ + {Key: "key", RegexPattern: "(?P.*?)$", FromAttribute: "aa", Action: EXTRACT}, + }, + errorString: "error creating AttrProc. Action \"extract\" does not use \"value\" or \"from_attribute\" field. These must not be specified for 0-th action", + }, + { + name: "invalid regex", + actionLists: []ActionKeyValue{ + {Key: "aa", RegexPattern: "(?P.*?)$", Action: EXTRACT}, + }, + errorString: "error creating AttrProc. Field \"pattern\" has invalid pattern: \"(?P.*?)$\" to be set at the 0-th actions", + }, + { + name: "delete with regex", + actionLists: []ActionKeyValue{ + {RegexPattern: "(?P.*?)$", Key: "ab", Action: DELETE}, + }, + errorString: "error creating AttrProc. Action \"delete\" does not use \"value\", \"pattern\" or \"from_attribute\" field. These must not be specified for 0-th action", + }, + { + name: "regex with unnamed capture group", + actionLists: []ActionKeyValue{ + {Key: "aa", RegexPattern: ".*$", Action: EXTRACT}, + }, + errorString: "error creating AttrProc. Field \"pattern\" contains no named matcher groups at the 0-th actions", + }, + { + name: "regex with one unnamed capture groups", + actionLists: []ActionKeyValue{ + {Key: "aa", RegexPattern: "^\\/api\\/v1\\/document\\/(?P.*)\\/update\\/(.*)$", Action: EXTRACT}, + }, + errorString: "error creating AttrProc. Field \"pattern\" contains at least one unnamed matcher group at the 0-th actions", + }, + } + + for _, tc := range testcase { + t.Run(tc.name, func(t *testing.T) { + ap, err := NewAttrProc(&Settings{Actions: tc.actionLists}) + assert.Nil(t, ap) + assert.EqualValues(t, errors.New(tc.errorString), err) + }) + } +} + +func TestValidConfiguration(t *testing.T) { + cfg := &Settings{ + Actions: []ActionKeyValue{ + {Key: "one", Action: "Delete"}, + {Key: "two", Value: 123, Action: "INSERT"}, + {Key: "three", FromAttribute: "two", Action: "upDaTE"}, + {Key: "five", FromAttribute: "two", Action: "upsert"}, + {Key: "two", RegexPattern: "^\\/api\\/v1\\/document\\/(?P.*)\\/update$", Action: "EXTRact"}, + }, + } + ap, err := NewAttrProc(cfg) + require.NoError(t, err) + + av := pdata.NewAttributeValueInt(123) + compiledRegex := regexp.MustCompile(`^\/api\/v1\/document\/(?P.*)\/update$`) + assert.Equal(t, []attributeAction{ + {Key: "one", Action: DELETE}, + {Key: "two", Action: INSERT, + AttributeValue: &av, + }, + {Key: "three", FromAttribute: "two", Action: UPDATE}, + {Key: "five", FromAttribute: "two", Action: UPSERT}, + {Key: "two", Regex: compiledRegex, AttrNames: []string{"", "documentId"}, Action: EXTRACT}, + }, ap.actions) + +} + +func sha1Hash(b []byte) string { + // #nosec + h := sha1.New() + h.Write(b) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/internal/otel_collector/processor/processorhelper/factory.go b/internal/otel_collector/processor/processorhelper/factory.go new file mode 100644 index 00000000000..c5175f762d2 --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/factory.go @@ -0,0 +1,149 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + "context" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +// FactoryOption apply changes to ProcessorOptions. +type FactoryOption func(o *factory) + +// CreateDefaultConfig is the equivalent of component.ProcessorFactory.CreateDefaultConfig() +type CreateDefaultConfig func() configmodels.Processor + +// CreateTraceProcessor is the equivalent of component.ProcessorFactory.CreateTracesProcessor() +type CreateTraceProcessor func(context.Context, component.ProcessorCreateParams, configmodels.Processor, consumer.TracesConsumer) (component.TracesProcessor, error) + +// CreateMetricsProcessor is the equivalent of component.ProcessorFactory.CreateMetricsProcessor() +type CreateMetricsProcessor func(context.Context, component.ProcessorCreateParams, configmodels.Processor, consumer.MetricsConsumer) (component.MetricsProcessor, error) + +// CreateLogsProcessor is the equivalent of component.ProcessorFactory.CreateLogsProcessor() +type CreateLogsProcessor func(context.Context, component.ProcessorCreateParams, configmodels.Processor, consumer.LogsConsumer) (component.LogsProcessor, error) + +type factory struct { + cfgType configmodels.Type + customUnmarshaler component.CustomUnmarshaler + createDefaultConfig CreateDefaultConfig + createTraceProcessor CreateTraceProcessor + createMetricsProcessor CreateMetricsProcessor + createLogsProcessor CreateLogsProcessor +} + +// WithCustomUnmarshaler implements component.ConfigUnmarshaler. +func WithCustomUnmarshaler(customUnmarshaler component.CustomUnmarshaler) FactoryOption { + return func(o *factory) { + o.customUnmarshaler = customUnmarshaler + } +} + +// WithTraces overrides the default "error not supported" implementation for CreateTraceProcessor. +func WithTraces(createTraceProcessor CreateTraceProcessor) FactoryOption { + return func(o *factory) { + o.createTraceProcessor = createTraceProcessor + } +} + +// WithMetrics overrides the default "error not supported" implementation for CreateMetricsProcessor. +func WithMetrics(createMetricsProcessor CreateMetricsProcessor) FactoryOption { + return func(o *factory) { + o.createMetricsProcessor = createMetricsProcessor + } +} + +// WithLogs overrides the default "error not supported" implementation for CreateLogsProcessor. +func WithLogs(createLogsProcessor CreateLogsProcessor) FactoryOption { + return func(o *factory) { + o.createLogsProcessor = createLogsProcessor + } +} + +// NewFactory returns a component.ProcessorFactory. +func NewFactory( + cfgType configmodels.Type, + createDefaultConfig CreateDefaultConfig, + options ...FactoryOption) component.ProcessorFactory { + f := &factory{ + cfgType: cfgType, + createDefaultConfig: createDefaultConfig, + } + for _, opt := range options { + opt(f) + } + var ret component.ProcessorFactory + if f.customUnmarshaler != nil { + ret = &factoryWithUnmarshaler{f} + } else { + ret = f + } + return ret +} + +// Type gets the type of the Processor config created by this factory. +func (f *factory) Type() configmodels.Type { + return f.cfgType +} + +// CreateDefaultConfig creates the default configuration for processor. +func (f *factory) CreateDefaultConfig() configmodels.Processor { + return f.createDefaultConfig() +} + +// CreateTraceProcessor creates a component.TracesProcessor based on this config. +func (f *factory) CreateTracesProcessor(ctx context.Context, params component.ProcessorCreateParams, cfg configmodels.Processor, nextConsumer consumer.TracesConsumer) (component.TracesProcessor, error) { + if f.createTraceProcessor != nil { + return f.createTraceProcessor(ctx, params, cfg, nextConsumer) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsProcessor creates a consumer.MetricsConsumer based on this config. +func (f *factory) CreateMetricsProcessor(ctx context.Context, params component.ProcessorCreateParams, cfg configmodels.Processor, nextConsumer consumer.MetricsConsumer) (component.MetricsProcessor, error) { + if f.createMetricsProcessor != nil { + return f.createMetricsProcessor(ctx, params, cfg, nextConsumer) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateLogsProcessor creates a metrics processor based on this config. +func (f *factory) CreateLogsProcessor( + ctx context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.LogsConsumer, +) (component.LogsProcessor, error) { + if f.createLogsProcessor != nil { + return f.createLogsProcessor(ctx, params, cfg, nextConsumer) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +var _ component.ConfigUnmarshaler = (*factoryWithUnmarshaler)(nil) + +type factoryWithUnmarshaler struct { + *factory +} + +// Unmarshal un-marshals the config using the provided custom unmarshaler. +func (f *factoryWithUnmarshaler) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error { + return f.customUnmarshaler(componentViperSection, intoCfg) +} diff --git a/internal/otel_collector/processor/processorhelper/factory_test.go b/internal/otel_collector/processor/processorhelper/factory_test.go new file mode 100644 index 00000000000..690a334fbbe --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/factory_test.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + "context" + "errors" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +const typeStr = "test" + +var defaultCfg = &configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, +} + +func TestNewTrace(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + _, ok := factory.(component.ConfigUnmarshaler) + assert.False(t, ok) + _, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, defaultCfg, nil) + assert.Error(t, err) + _, err = factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, defaultCfg, nil) + assert.Error(t, err) + _, err = factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, defaultCfg, nil) + assert.Error(t, err) +} + +func TestNewMetrics_WithConstructors(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig, + WithTraces(createTraceProcessor), + WithMetrics(createMetricsProcessor), + WithLogs(createLogsProcessor), + WithCustomUnmarshaler(customUnmarshaler)) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + + fu, ok := factory.(component.ConfigUnmarshaler) + assert.True(t, ok) + assert.Equal(t, errors.New("my error"), fu.Unmarshal(nil, nil)) + + _, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, defaultCfg, nil) + assert.NoError(t, err) + + _, err = factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, defaultCfg, nil) + assert.NoError(t, err) + + _, err = factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, defaultCfg, nil) + assert.NoError(t, err) +} + +func defaultConfig() configmodels.Processor { + return defaultCfg +} + +func createTraceProcessor(context.Context, component.ProcessorCreateParams, configmodels.Processor, consumer.TracesConsumer) (component.TracesProcessor, error) { + return nil, nil +} + +func createMetricsProcessor(context.Context, component.ProcessorCreateParams, configmodels.Processor, consumer.MetricsConsumer) (component.MetricsProcessor, error) { + return nil, nil +} + +func createLogsProcessor(context.Context, component.ProcessorCreateParams, configmodels.Processor, consumer.LogsConsumer) (component.LogsProcessor, error) { + return nil, nil +} + +func customUnmarshaler(*viper.Viper, interface{}) error { + return errors.New("my error") +} diff --git a/internal/otel_collector/processor/processorhelper/hasher.go b/internal/otel_collector/processor/processorhelper/hasher.go new file mode 100644 index 00000000000..6f0999a9568 --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/hasher.go @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + // #nosec + "crypto/sha1" + "encoding/binary" + "encoding/hex" + "math" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const ( + int64ByteSize = 8 + float64ByteSize = 8 +) + +var ( + byteTrue = [1]byte{1} + byteFalse = [1]byte{0} +) + +// sha1Hasher hashes an AttributeValue using SHA1 and returns a +// hashed version of the attribute. In practice, this would mostly be used +// for string attributes but we support all types for completeness/correctness +// and eliminate any surprises. +func sha1Hasher(attr pdata.AttributeValue) { + var val []byte + switch attr.Type() { + case pdata.AttributeValueSTRING: + val = []byte(attr.StringVal()) + case pdata.AttributeValueBOOL: + if attr.BoolVal() { + val = byteTrue[:] + } else { + val = byteFalse[:] + } + case pdata.AttributeValueINT: + val = make([]byte, int64ByteSize) + binary.LittleEndian.PutUint64(val, uint64(attr.IntVal())) + case pdata.AttributeValueDOUBLE: + val = make([]byte, float64ByteSize) + binary.LittleEndian.PutUint64(val, math.Float64bits(attr.DoubleVal())) + } + + var hashed string + if len(val) > 0 { + // #nosec + h := sha1.New() + h.Write(val) + val = h.Sum(nil) + hashedBytes := make([]byte, hex.EncodedLen(len(val))) + hex.Encode(hashedBytes, val) + hashed = string(hashedBytes) + } + + attr.SetStringVal(hashed) +} diff --git a/internal/otel_collector/processor/processorhelper/processor.go b/internal/otel_collector/processor/processorhelper/processor.go new file mode 100644 index 00000000000..0846d9d2846 --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/processor.go @@ -0,0 +1,256 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + "context" + "errors" + + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/component/componenthelper" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" +) + +// ErrSkipProcessingData is a sentinel value to indicate when traces or metrics should intentionally be dropped +// from further processing in the pipeline because the data is determined to be irrelevant. A processor can return this error +// to stop further processing without propagating an error back up the pipeline to logs. +var ErrSkipProcessingData = errors.New("sentinel error to skip processing data from the remainder of the pipeline") + +// TProcessor is a helper interface that allows avoiding implementing all functions in TracesProcessor by using NewTraceProcessor. +type TProcessor interface { + // ProcessTraces is a helper function that processes the incoming data and returns the data to be sent to the next component. + // If error is returned then returned data are ignored. It MUST not call the next component. + ProcessTraces(context.Context, pdata.Traces) (pdata.Traces, error) +} + +// MProcessor is a helper interface that allows avoiding implementing all functions in MetricsProcessor by using NewTraceProcessor. +type MProcessor interface { + // ProcessMetrics is a helper function that processes the incoming data and returns the data to be sent to the next component. + // If error is returned then returned data are ignored. It MUST not call the next component. + ProcessMetrics(context.Context, pdata.Metrics) (pdata.Metrics, error) +} + +// LProcessor is a helper interface that allows avoiding implementing all functions in LogsProcessor by using NewLogsProcessor. +type LProcessor interface { + // ProcessLogs is a helper function that processes the incoming data and returns the data to be sent to the next component. + // If error is returned then returned data are ignored. It MUST not call the next component. + ProcessLogs(context.Context, pdata.Logs) (pdata.Logs, error) +} + +// Option apply changes to internalOptions. +type Option func(*baseSettings) + +// WithStart overrides the default Start function for an processor. +// The default shutdown function does nothing and always returns nil. +func WithStart(start componenthelper.Start) Option { + return func(o *baseSettings) { + o.Start = start + } +} + +// WithShutdown overrides the default Shutdown function for an processor. +// The default shutdown function does nothing and always returns nil. +func WithShutdown(shutdown componenthelper.Shutdown) Option { + return func(o *baseSettings) { + o.Shutdown = shutdown + } +} + +// WithShutdown overrides the default GetCapabilities function for an processor. +// The default GetCapabilities function returns mutable capabilities. +func WithCapabilities(capabilities component.ProcessorCapabilities) Option { + return func(o *baseSettings) { + o.capabilities = capabilities + } +} + +type baseSettings struct { + *componenthelper.ComponentSettings + capabilities component.ProcessorCapabilities +} + +// fromOptions returns the internal settings starting from the default and applying all options. +func fromOptions(options []Option) *baseSettings { + // Start from the default options: + opts := &baseSettings{ + ComponentSettings: componenthelper.DefaultComponentSettings(), + capabilities: component.ProcessorCapabilities{MutatesConsumedData: true}, + } + + for _, op := range options { + op(opts) + } + + return opts +} + +// internalOptions contains internalOptions concerning how an Processor is configured. +type baseProcessor struct { + component.Component + fullName string + capabilities component.ProcessorCapabilities + traceAttributes []trace.Attribute +} + +// Construct the internalOptions from multiple Option. +func newBaseProcessor(fullName string, options ...Option) baseProcessor { + bs := fromOptions(options) + be := baseProcessor{ + Component: componenthelper.NewComponent(bs.ComponentSettings), + fullName: fullName, + capabilities: bs.capabilities, + traceAttributes: []trace.Attribute{ + trace.StringAttribute(obsreport.ProcessorKey, fullName), + }, + } + + return be +} + +func (bp *baseProcessor) GetCapabilities() component.ProcessorCapabilities { + return bp.capabilities +} + +type tracesProcessor struct { + baseProcessor + processor TProcessor + nextConsumer consumer.TracesConsumer +} + +func (tp *tracesProcessor) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + span := trace.FromContext(ctx) + span.Annotate(tp.traceAttributes, "Start processing.") + var err error + td, err = tp.processor.ProcessTraces(ctx, td) + span.Annotate(tp.traceAttributes, "End processing.") + if err != nil { + return err + } + return tp.nextConsumer.ConsumeTraces(ctx, td) +} + +// NewTraceProcessor creates a TracesProcessor that ensure context propagation and the right tags are set. +// TODO: Add observability metrics support +func NewTraceProcessor( + config configmodels.Processor, + nextConsumer consumer.TracesConsumer, + processor TProcessor, + options ...Option, +) (component.TracesProcessor, error) { + if processor == nil { + return nil, errors.New("nil processor") + } + + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + return &tracesProcessor{ + baseProcessor: newBaseProcessor(config.Name(), options...), + processor: processor, + nextConsumer: nextConsumer, + }, nil +} + +type metricsProcessor struct { + baseProcessor + processor MProcessor + nextConsumer consumer.MetricsConsumer +} + +func (mp *metricsProcessor) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + span := trace.FromContext(ctx) + span.Annotate(mp.traceAttributes, "Start processing.") + var err error + md, err = mp.processor.ProcessMetrics(ctx, md) + span.Annotate(mp.traceAttributes, "End processing.") + if err != nil { + if err == ErrSkipProcessingData { + return nil + } + return err + } + return mp.nextConsumer.ConsumeMetrics(ctx, md) +} + +// NewMetricsProcessor creates a MetricsProcessor that ensure context propagation and the right tags are set. +// TODO: Add observability metrics support +func NewMetricsProcessor( + config configmodels.Processor, + nextConsumer consumer.MetricsConsumer, + processor MProcessor, + options ...Option, +) (component.MetricsProcessor, error) { + if processor == nil { + return nil, errors.New("nil processor") + } + + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + return &metricsProcessor{ + baseProcessor: newBaseProcessor(config.Name(), options...), + processor: processor, + nextConsumer: nextConsumer, + }, nil +} + +type logProcessor struct { + baseProcessor + processor LProcessor + nextConsumer consumer.LogsConsumer +} + +func (lp *logProcessor) ConsumeLogs(ctx context.Context, ld pdata.Logs) error { + span := trace.FromContext(ctx) + span.Annotate(lp.traceAttributes, "Start processing.") + var err error + ld, err = lp.processor.ProcessLogs(ctx, ld) + span.Annotate(lp.traceAttributes, "End processing.") + if err != nil { + return err + } + return lp.nextConsumer.ConsumeLogs(ctx, ld) +} + +// NewLogsProcessor creates a LogsProcessor that ensure context propagation and the right tags are set. +// TODO: Add observability metrics support +func NewLogsProcessor( + config configmodels.Processor, + nextConsumer consumer.LogsConsumer, + processor LProcessor, + options ...Option, +) (component.LogsProcessor, error) { + if processor == nil { + return nil, errors.New("nil processor") + } + + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + return &logProcessor{ + baseProcessor: newBaseProcessor(config.Name(), options...), + processor: processor, + nextConsumer: nextConsumer, + }, nil +} diff --git a/internal/otel_collector/processor/processorhelper/processor_test.go b/internal/otel_collector/processor/processorhelper/processor_test.go new file mode 100644 index 00000000000..ae5fe637c04 --- /dev/null +++ b/internal/otel_collector/processor/processorhelper/processor_test.go @@ -0,0 +1,171 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processorhelper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +const testFullName = "testFullName" + +var testCfg = &configmodels.ProcessorSettings{ + TypeVal: testFullName, + NameVal: testFullName, +} + +func TestDefaultOptions(t *testing.T) { + bp := newBaseProcessor(testFullName) + assert.True(t, bp.GetCapabilities().MutatesConsumedData) + assert.NoError(t, bp.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, bp.Shutdown(context.Background())) +} + +func TestWithOptions(t *testing.T) { + want := errors.New("my_error") + bp := newBaseProcessor(testFullName, + WithStart(func(context.Context, component.Host) error { return want }), + WithShutdown(func(context.Context) error { return want }), + WithCapabilities(component.ProcessorCapabilities{MutatesConsumedData: false})) + assert.Equal(t, want, bp.Start(context.Background(), componenttest.NewNopHost())) + assert.Equal(t, want, bp.Shutdown(context.Background())) + assert.False(t, bp.GetCapabilities().MutatesConsumedData) +} + +func TestNewTraceExporter(t *testing.T) { + me, err := NewTraceProcessor(testCfg, consumertest.NewTracesNop(), newTestTProcessor(nil)) + require.NoError(t, err) + + assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, me.ConsumeTraces(context.Background(), testdata.GenerateTraceDataEmpty())) + assert.NoError(t, me.Shutdown(context.Background())) +} + +func TestNewTraceExporter_NilRequiredFields(t *testing.T) { + _, err := NewTraceProcessor(testCfg, consumertest.NewTracesNop(), nil) + assert.Error(t, err) + + _, err = NewTraceProcessor(testCfg, nil, newTestTProcessor(nil)) + assert.Equal(t, componenterror.ErrNilNextConsumer, err) +} + +func TestNewTraceExporter_ProcessTraceError(t *testing.T) { + want := errors.New("my_error") + me, err := NewTraceProcessor(testCfg, consumertest.NewTracesNop(), newTestTProcessor(want)) + require.NoError(t, err) + assert.Equal(t, want, me.ConsumeTraces(context.Background(), testdata.GenerateTraceDataEmpty())) +} + +func TestNewMetricsExporter(t *testing.T) { + me, err := NewMetricsProcessor(testCfg, consumertest.NewMetricsNop(), newTestMProcessor(nil)) + require.NoError(t, err) + + assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, me.ConsumeMetrics(context.Background(), testdata.GenerateMetricsEmpty())) + assert.NoError(t, me.Shutdown(context.Background())) +} + +func TestNewMetricsExporter_NilRequiredFields(t *testing.T) { + _, err := NewMetricsProcessor(testCfg, consumertest.NewMetricsNop(), nil) + assert.Error(t, err) + + _, err = NewMetricsProcessor(testCfg, nil, newTestMProcessor(nil)) + assert.Equal(t, componenterror.ErrNilNextConsumer, err) +} + +func TestNewMetricsExporter_ProcessMetricsError(t *testing.T) { + want := errors.New("my_error") + me, err := NewMetricsProcessor(testCfg, consumertest.NewMetricsNop(), newTestMProcessor(want)) + require.NoError(t, err) + assert.Equal(t, want, me.ConsumeMetrics(context.Background(), testdata.GenerateMetricsEmpty())) +} + +func TestNewMetricsExporter_ProcessMetricsErrSkipProcessingData(t *testing.T) { + me, err := NewMetricsProcessor(testCfg, consumertest.NewMetricsNop(), newTestMProcessor(ErrSkipProcessingData)) + require.NoError(t, err) + assert.Equal(t, nil, me.ConsumeMetrics(context.Background(), testdata.GenerateMetricsEmpty())) +} + +func TestNewLogsExporter(t *testing.T) { + me, err := NewLogsProcessor(testCfg, consumertest.NewLogsNop(), newTestLProcessor(nil)) + require.NoError(t, err) + + assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, me.ConsumeLogs(context.Background(), testdata.GenerateLogDataEmpty())) + assert.NoError(t, me.Shutdown(context.Background())) +} + +func TestNewLogsExporter_NilRequiredFields(t *testing.T) { + _, err := NewLogsProcessor(testCfg, consumertest.NewLogsNop(), nil) + assert.Error(t, err) + + _, err = NewLogsProcessor(testCfg, nil, newTestLProcessor(nil)) + assert.Equal(t, componenterror.ErrNilNextConsumer, err) +} + +func TestNewLogsExporter_ProcessLogError(t *testing.T) { + want := errors.New("my_error") + me, err := NewLogsProcessor(testCfg, consumertest.NewLogsNop(), newTestLProcessor(want)) + require.NoError(t, err) + assert.Equal(t, want, me.ConsumeLogs(context.Background(), testdata.GenerateLogDataEmpty())) +} + +type testTProcessor struct { + retError error +} + +func newTestTProcessor(retError error) TProcessor { + return &testTProcessor{retError: retError} +} + +func (ttp *testTProcessor) ProcessTraces(_ context.Context, td pdata.Traces) (pdata.Traces, error) { + return td, ttp.retError +} + +type testMProcessor struct { + retError error +} + +func newTestMProcessor(retError error) MProcessor { + return &testMProcessor{retError: retError} +} + +func (tmp *testMProcessor) ProcessMetrics(_ context.Context, md pdata.Metrics) (pdata.Metrics, error) { + return md, tmp.retError +} + +type testLProcessor struct { + retError error +} + +func newTestLProcessor(retError error) LProcessor { + return &testLProcessor{retError: retError} +} + +func (tlp *testLProcessor) ProcessLogs(_ context.Context, ld pdata.Logs) (pdata.Logs, error) { + return ld, tlp.retError +} diff --git a/internal/otel_collector/processor/queuedprocessor/README.md b/internal/otel_collector/processor/queuedprocessor/README.md new file mode 100644 index 00000000000..9d50c03d04c --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/README.md @@ -0,0 +1,3 @@ +# Queued Retry Processor + +QueuedRetry processor is deprecated. Use exporter queued retry config. diff --git a/internal/otel_collector/processor/queuedprocessor/config.go b/internal/otel_collector/processor/queuedprocessor/config.go new file mode 100644 index 00000000000..47dbb271aaa --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/config.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "time" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for Attributes processor. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + + // NumConsumers is the number of queue workers that dequeue batches and send them out. + NumWorkers int `mapstructure:"num_workers"` + // QueueSize is the maximum number of batches allowed in queue at a given time. + QueueSize int `mapstructure:"queue_size"` + // Retry indicates whether queue processor should retry span batches in case of processing failure. + RetryOnFailure bool `mapstructure:"retry_on_failure"` + // BackoffDelay is the amount of time a worker waits after a failed send before retrying. + BackoffDelay time.Duration `mapstructure:"backoff_delay"` +} diff --git a/internal/otel_collector/processor/queuedprocessor/config_test.go b/internal/otel_collector/processor/queuedprocessor/config_test.go new file mode 100644 index 00000000000..f07ee9fc898 --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/config_test.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + p0 := cfg.Processors["queued_retry"] + assert.Equal(t, p0, factory.CreateDefaultConfig()) + + p1 := cfg.Processors["queued_retry/2"] + assert.Equal(t, p1, + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "queued_retry", + NameVal: "queued_retry/2", + }, + NumWorkers: 2, + QueueSize: 10, + RetryOnFailure: true, + BackoffDelay: time.Second * 5, + }) +} diff --git a/internal/otel_collector/processor/queuedprocessor/factory.go b/internal/otel_collector/processor/queuedprocessor/factory.go new file mode 100644 index 00000000000..482ecc57a54 --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/factory.go @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "queued_retry" +) + +// NewFactory returns a new factory for the Queued processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor), + processorhelper.WithMetrics(createMetricsProcessor)) +} + +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + NumWorkers: 10, + QueueSize: 5000, + RetryOnFailure: true, + BackoffDelay: time.Second * 5, + } +} + +func createTraceProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, +) (component.TracesProcessor, error) { + params.Logger.Warn("QueuedRetry processor is deprecated. Use exporter's queued retry config.") + return newQueuedTracesProcessor(params, nextConsumer, cfg.(*Config)), nil +} + +func createMetricsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsProcessor, error) { + params.Logger.Warn("QueuedRetry processor is deprecated. Use exporter's queued retry config.") + return newQueuedMetricsProcessor(params, nextConsumer, cfg.(*Config)), nil +} diff --git a/internal/otel_collector/processor/queuedprocessor/factory_test.go b/internal/otel_collector/processor/queuedprocessor/factory_test.go new file mode 100644 index 00000000000..a4a69cc9f8b --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/factory_test.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + tp, err := factory.CreateTracesProcessor(context.Background(), creationParams, cfg, nil) + assert.NotNil(t, tp) + assert.NoError(t, err, "cannot create trace processor") + + mp, err := factory.CreateMetricsProcessor(context.Background(), creationParams, cfg, nil) + assert.NotNil(t, mp) + assert.NoError(t, err, "cannot create metrics processor") +} diff --git a/internal/otel_collector/processor/queuedprocessor/metrics.go b/internal/otel_collector/processor/queuedprocessor/metrics.go new file mode 100644 index 00000000000..ccb39e1b4b4 --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/metrics.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/processor" +) + +// Variables related to metrics specific to queued processor. +var ( + statInQueueLatencyMs = stats.Int64("queue_latency", "Latency (in milliseconds) that a batch stayed in queue", stats.UnitMilliseconds) + statSendLatencyMs = stats.Int64("send_latency", "Latency (in milliseconds) to send a batch", stats.UnitMilliseconds) + statSuccessSendOps = stats.Int64("success_send", "Number of successful send operations", stats.UnitDimensionless) + statFailedSendOps = stats.Int64("fail_send", "Number of failed send operations", stats.UnitDimensionless) + statQueueLength = stats.Int64("queue_length", "Current length of the queue (in batches)", stats.UnitDimensionless) + + latencyDistributionAggregation = view.Distribution(10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 10000, 20000, 30000, 50000) + + queueLengthView = &view.View{ + Name: statQueueLength.Name(), + Measure: statQueueLength, + Description: "Current number of batches in the queue", + TagKeys: []tag.Key{processor.TagProcessorNameKey}, + Aggregation: view.LastValue(), + } + sendLatencyView = &view.View{ + Name: statSendLatencyMs.Name(), + Measure: statSendLatencyMs, + Description: "The latency of the successful send operations.", + TagKeys: []tag.Key{processor.TagProcessorNameKey}, + Aggregation: latencyDistributionAggregation, + } + inQueueLatencyView = &view.View{ + Name: statInQueueLatencyMs.Name(), + Measure: statInQueueLatencyMs, + Description: "The \"in queue\" latency of the successful send operations.", + TagKeys: []tag.Key{processor.TagProcessorNameKey}, + Aggregation: latencyDistributionAggregation, + } +) + +// MetricViews return the metrics views according to given telemetry level. +func MetricViews() []*view.View { + tagKeys := processor.MetricTagKeys() + + countSuccessSendView := &view.View{ + Name: statSuccessSendOps.Name(), + Measure: statSuccessSendOps, + Description: "The number of successful send operations performed by queued_retry processor", + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + countFailuresSendView := &view.View{ + Name: statFailedSendOps.Name(), + Measure: statFailedSendOps, + Description: "The number of failed send operations performed by queued_retry processor", + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + + legacyViews := []*view.View{queueLengthView, countSuccessSendView, countFailuresSendView, sendLatencyView, inQueueLatencyView} + + return obsreport.ProcessorMetricViews(typeStr, legacyViews) +} diff --git a/internal/otel_collector/processor/queuedprocessor/queued_processor.go b/internal/otel_collector/processor/queuedprocessor/queued_processor.go new file mode 100644 index 00000000000..cbe52810084 --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/queued_processor.go @@ -0,0 +1,345 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/jaegertracing/jaeger/pkg/queue" + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/processor" +) + +type queuedProcessor struct { + name string + queue *queue.BoundedQueue + logger *zap.Logger + traceNext consumer.TracesConsumer + metricNext consumer.MetricsConsumer + numWorkers int + retryOnProcessingFailure bool + backoffDelay time.Duration + stopCh chan struct{} + stopOnce sync.Once + obsrep *obsreport.ProcessorObsReport +} + +var _ consumer.TracesConsumer = (*queuedProcessor)(nil) +var errorRefused = errors.New("failed to add to the queue") + +type queueItem interface { + context() context.Context + queuedTime() time.Time + export(sp *queuedProcessor) error + onAccepted() + // Returns a new queue item that contains the items left to be exported. + onPartialError(partialErr consumererror.PartialError) queueItem + onRefused(logger *zap.Logger, err error) + onDropped(logger *zap.Logger, err error) +} + +type baseQueueItem struct { + ctx context.Context + qt time.Time + obsrep *obsreport.ProcessorObsReport +} + +func (item *baseQueueItem) context() context.Context { + return item.ctx +} + +func (item *baseQueueItem) queuedTime() time.Time { + return item.qt +} + +type traceQueueItem struct { + baseQueueItem + td pdata.Traces + spanCountStats *processor.SpanCountStats +} + +func newTraceQueueItem(ctx context.Context, td pdata.Traces, obsrep *obsreport.ProcessorObsReport) queueItem { + return &traceQueueItem{ + baseQueueItem: baseQueueItem{ctx: ctx, qt: time.Now(), obsrep: obsrep}, + td: td, + spanCountStats: processor.NewSpanCountStats(td), + } +} + +func (item *traceQueueItem) onAccepted() { + processor.RecordsSpanCountMetrics(item.ctx, item.spanCountStats, processor.StatReceivedSpanCount) + item.obsrep.TracesAccepted(item.ctx, item.spanCountStats.GetAllSpansCount()) +} + +func (item *traceQueueItem) onPartialError(partialErr consumererror.PartialError) queueItem { + return newTraceQueueItem(item.ctx, partialErr.GetTraces(), item.obsrep) +} + +func (item *traceQueueItem) onRefused(logger *zap.Logger, err error) { + // Count the StatReceivedSpanCount even if items were refused. + processor.RecordsSpanCountMetrics(item.ctx, item.spanCountStats, processor.StatReceivedSpanCount) + + item.obsrep.TracesRefused(item.ctx, item.spanCountStats.GetAllSpansCount()) + + // TODO: in principle this may not end in data loss because this can be + // in the same call stack as the receiver, ie.: the call from the receiver + // to here is synchronous. This means that actually it could be proper to + // record this as "refused" instead of "dropped". + stats.Record(item.ctx, processor.StatTraceBatchesDroppedCount.M(int64(1))) + processor.RecordsSpanCountMetrics(item.ctx, item.spanCountStats, processor.StatDroppedSpanCount) + + logger.Error("Failed to process batch, refused", zap.Int("#spans", item.spanCountStats.GetAllSpansCount()), zap.Error(err)) +} + +func (item *traceQueueItem) onDropped(logger *zap.Logger, err error) { + item.obsrep.TracesDropped(item.ctx, item.spanCountStats.GetAllSpansCount()) + + stats.Record(item.ctx, processor.StatTraceBatchesDroppedCount.M(int64(1))) + processor.RecordsSpanCountMetrics(item.ctx, item.spanCountStats, processor.StatDroppedSpanCount) + logger.Error("Failed to process batch, discarding", zap.Int("#spans", item.spanCountStats.GetAllSpansCount()), zap.Error(err)) +} + +func (item *traceQueueItem) export(sp *queuedProcessor) error { + return sp.traceNext.ConsumeTraces(item.ctx, item.td) +} + +type metricsQueueItem struct { + baseQueueItem + md pdata.Metrics + numPoints int +} + +func newMetricsQueueItem(ctx context.Context, md pdata.Metrics, obsrep *obsreport.ProcessorObsReport) queueItem { + _, numPoints := md.MetricAndDataPointCount() + return &metricsQueueItem{ + baseQueueItem: baseQueueItem{ctx: ctx, qt: time.Now(), obsrep: obsrep}, + md: md, + numPoints: numPoints, + } +} + +func (item *metricsQueueItem) onAccepted() { + item.obsrep.MetricsAccepted(item.ctx, item.numPoints) +} + +func (item *metricsQueueItem) onPartialError(consumererror.PartialError) queueItem { + // TODO: implement this. + return item +} + +func (item *metricsQueueItem) onRefused(logger *zap.Logger, err error) { + item.obsrep.MetricsRefused(item.ctx, item.numPoints) + + logger.Error("Failed to process batch, refused", zap.Int("#points", item.numPoints), zap.Error(err)) +} + +func (item *metricsQueueItem) onDropped(logger *zap.Logger, err error) { + stats.Record(item.ctx, processor.StatTraceBatchesDroppedCount.M(int64(1))) + item.obsrep.MetricsDropped(item.ctx, item.numPoints) + + logger.Error("Failed to process batch, discarding", zap.Int("#points", item.numPoints), zap.Error(err)) +} + +func (item *metricsQueueItem) export(sp *queuedProcessor) error { + return sp.metricNext.ConsumeMetrics(item.ctx, item.md) +} + +func newQueuedTracesProcessor( + params component.ProcessorCreateParams, + nextConsumer consumer.TracesConsumer, + cfg *Config, +) *queuedProcessor { + return &queuedProcessor{ + name: cfg.Name(), + queue: queue.NewBoundedQueue(cfg.QueueSize, func(item interface{}) {}), + logger: params.Logger, + numWorkers: cfg.NumWorkers, + traceNext: nextConsumer, + metricNext: nil, + retryOnProcessingFailure: cfg.RetryOnFailure, + backoffDelay: cfg.BackoffDelay, + stopCh: make(chan struct{}), + obsrep: obsreport.NewProcessorObsReport(configtelemetry.GetMetricsLevelFlagValue(), cfg.Name()), + } +} + +func newQueuedMetricsProcessor( + params component.ProcessorCreateParams, + nextConsumer consumer.MetricsConsumer, + cfg *Config, +) *queuedProcessor { + return &queuedProcessor{ + name: cfg.Name(), + queue: queue.NewBoundedQueue(cfg.QueueSize, func(item interface{}) {}), + logger: params.Logger, + numWorkers: cfg.NumWorkers, + traceNext: nil, + metricNext: nextConsumer, + retryOnProcessingFailure: cfg.RetryOnFailure, + backoffDelay: cfg.BackoffDelay, + stopCh: make(chan struct{}), + obsrep: obsreport.NewProcessorObsReport(configtelemetry.GetMetricsLevelFlagValue(), cfg.Name()), + } +} + +// Start is invoked during service startup. +func (sp *queuedProcessor) Start(ctx context.Context, _ component.Host) error { + // emit 0's so that the metric is present and reported, rather than absent + statsTags := []tag.Mutator{tag.Insert(processor.TagProcessorNameKey, sp.name)} + _ = stats.RecordWithTags( + ctx, + statsTags, + processor.StatTraceBatchesDroppedCount.M(int64(0)), + processor.StatDroppedSpanCount.M(int64(0))) + + sp.queue.StartConsumers(sp.numWorkers, func(item interface{}) { + value := item.(queueItem) + sp.processItemFromQueue(value) + }) + + // Start a timer to report the queue length. + ticker := time.NewTicker(1 * time.Second) + go func() { + defer ticker.Stop() + for { + select { + case <-sp.stopCh: + return + case <-ticker.C: + _ = stats.RecordWithTags( + context.Background(), + statsTags, + statQueueLength.M(int64(sp.queue.Size()))) + } + } + }() + + return nil +} + +// ConsumeTraces implements the TracesProcessor interface +func (sp *queuedProcessor) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + item := newTraceQueueItem(ctx, td, sp.obsrep) + + addedToQueue := sp.queue.Produce(item) + if !addedToQueue { + item.onRefused(sp.logger, errorRefused) + return errorRefused + } + + item.onAccepted() + return nil +} + +// ConsumeMetrics implements the MetricsProcessor interface +func (sp *queuedProcessor) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + item := newMetricsQueueItem(ctx, md, sp.obsrep) + + addedToQueue := sp.queue.Produce(item) + if !addedToQueue { + item.onRefused(sp.logger, errorRefused) + return errorRefused + } + + item.onAccepted() + return nil +} + +func (sp *queuedProcessor) GetCapabilities() component.ProcessorCapabilities { + return component.ProcessorCapabilities{MutatesConsumedData: false} +} + +// Shutdown is invoked during service shutdown. +func (sp *queuedProcessor) Shutdown(context.Context) error { + err := componenterror.ErrAlreadyStopped + sp.stopOnce.Do(func() { + err = nil + close(sp.stopCh) + sp.queue.Stop() + }) + return err +} + +func (sp *queuedProcessor) processItemFromQueue(item queueItem) { + startTime := time.Now() + err := item.export(sp) + if err == nil { + // Record latency metrics and return + sendLatencyMs := int64(time.Since(startTime) / time.Millisecond) + inQueueLatencyMs := int64(time.Since(item.queuedTime()) / time.Millisecond) + stats.Record(item.context(), + statSuccessSendOps.M(1), + statSendLatencyMs.M(sendLatencyMs), + statInQueueLatencyMs.M(inQueueLatencyMs)) + + return + } + + // There was an error + stats.Record(item.context(), statFailedSendOps.M(1)) + + // Immediately drop data on permanent errors. + if consumererror.IsPermanent(err) { + // throw away the batch + item.onDropped(sp.logger, err) + return + } + + // If partial error, update data and stats with non exported data. + if partialErr, isPartial := err.(consumererror.PartialError); isPartial { + item = item.onPartialError(partialErr) + } + + // Immediately drop data on no retries configured. + if !sp.retryOnProcessingFailure { + // throw away the batch + item.onDropped(sp.logger, fmt.Errorf("no retry processing %w", err)) + return + } + + // TODO: (@pjanotti) do not put it back on the end of the queue, retry with it directly. + // This will have the benefit of keeping the batch closer to related ones in time. + if !sp.queue.Produce(item) { + item.onDropped(sp.logger, fmt.Errorf("failed to re-enqueue: %w", err)) + return + } + + // back-off for configured delay, but get interrupted when shutting down + if sp.backoffDelay > 0 { + sp.logger.Warn("Backing off before next attempt", zap.Duration("backoff_delay", sp.backoffDelay)) + select { + case <-sp.stopCh: + sp.logger.Info("Interrupted due to shutdown") + break + case <-time.After(sp.backoffDelay): + sp.logger.Info("Resume processing") + break + } + } +} diff --git a/internal/otel_collector/processor/queuedprocessor/queued_processor_test.go b/internal/otel_collector/processor/queuedprocessor/queued_processor_test.go new file mode 100644 index 00000000000..9dc712263b2 --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/queued_processor_test.go @@ -0,0 +1,449 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package queuedprocessor + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/stats/view" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport/obsreporttest" + "go.opentelemetry.io/collector/processor" +) + +func TestTraceQueueProcessor_NoEnqueueOnPermanentError(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + td := testdata.GenerateTraceDataOneSpan() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(consumererror.Permanent(errors.New("bad data"))) + + cfg := createDefaultConfig().(*Config) + cfg.RetryOnFailure = true + cfg.BackoffDelay = time.Hour + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + qp := newQueuedTracesProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeTraces(context.Background(), td)) + }) + mockP.awaitAsyncProcessing() + <-time.After(200 * time.Millisecond) + require.Zero(t, qp.queue.Size()) + obsreporttest.CheckProcessorTracesViews(t, cfg.Name(), 1, 0, 1) +} + +func TestTraceQueueProcessor_EnqueueOnNoRetry(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + td := testdata.GenerateTraceDataOneSpan() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(errors.New("transient error")) + + cfg := createDefaultConfig().(*Config) + cfg.RetryOnFailure = false + cfg.BackoffDelay = 0 + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + qp := newQueuedTracesProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeTraces(context.Background(), td)) + }) + mockP.awaitAsyncProcessing() + <-time.After(200 * time.Millisecond) + require.Zero(t, qp.queue.Size()) + obsreporttest.CheckProcessorTracesViews(t, cfg.Name(), 1, 0, 1) +} + +func TestTraceQueueProcessor_PartialError(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + partialErr := consumererror.PartialTracesError(errors.New("some error"), testdata.GenerateTraceDataOneSpan()) + td := testdata.GenerateTraceDataTwoSpansSameResource() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(partialErr) + + cfg := createDefaultConfig().(*Config) + cfg.NumWorkers = 1 + cfg.RetryOnFailure = true + cfg.BackoffDelay = time.Second + + qp := newQueuedTracesProcessor(component.ProcessorCreateParams{Logger: zap.NewNop()}, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeTraces(context.Background(), td)) + }) + mockP.awaitAsyncProcessing() + // There is a small race condition in this test, but expect to execute this in less than 1 second. + mockP.updateError(nil) + mockP.waitGroup.Add(1) + mockP.awaitAsyncProcessing() + + mockP.checkNumBatches(t, 2) + mockP.checkNumSpans(t, 2+1) + + obsreporttest.CheckProcessorTracesViews(t, cfg.Name(), 2, 0, 0) +} + +func TestTraceQueueProcessor_EnqueueOnError(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + td := testdata.GenerateTraceDataOneSpan() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(errors.New("transient error")) + + cfg := createDefaultConfig().(*Config) + cfg.NumWorkers = 1 + cfg.QueueSize = 1 + cfg.RetryOnFailure = true + cfg.BackoffDelay = time.Hour + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + qp := newQueuedTracesProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeTraces(context.Background(), td)) + }) + mockP.awaitAsyncProcessing() + <-time.After(200 * time.Millisecond) + require.Equal(t, 1, qp.queue.Size()) + + mockP.run(func() { + // The queue is full, cannot enqueue other item + require.Error(t, qp.ConsumeTraces(context.Background(), td)) + }) + obsreporttest.CheckProcessorTracesViews(t, cfg.Name(), 1, 1, 0) +} + +func TestMetricsQueueProcessor_NoEnqueueOnPermanentError(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + md := testdata.GenerateMetricsTwoMetrics() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(consumererror.Permanent(errors.New("bad data"))) + + cfg := createDefaultConfig().(*Config) + cfg.RetryOnFailure = true + cfg.BackoffDelay = time.Hour + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + qp := newQueuedMetricsProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeMetrics(context.Background(), md)) + }) + mockP.awaitAsyncProcessing() + <-time.After(200 * time.Millisecond) + require.Zero(t, qp.queue.Size()) + obsreporttest.CheckProcessorMetricsViews(t, cfg.Name(), 4, 0, 4) +} + +func TestMetricsQueueProcessor_NoEnqueueOnNoRetry(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + md := testdata.GenerateMetricsTwoMetrics() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(errors.New("transient error")) + + cfg := createDefaultConfig().(*Config) + cfg.RetryOnFailure = false + cfg.BackoffDelay = 0 + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + qp := newQueuedMetricsProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeMetrics(context.Background(), md)) + }) + mockP.awaitAsyncProcessing() + <-time.After(200 * time.Millisecond) + require.Zero(t, qp.queue.Size()) + obsreporttest.CheckProcessorMetricsViews(t, cfg.Name(), 4, 0, 4) +} + +func TestMetricsQueueProcessor_EnqueueOnError(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + md := testdata.GenerateMetricsTwoMetrics() + + mockP := newMockConcurrentSpanProcessor() + mockP.updateError(errors.New("transient error")) + + cfg := createDefaultConfig().(*Config) + cfg.NumWorkers = 1 + cfg.QueueSize = 1 + cfg.RetryOnFailure = true + cfg.BackoffDelay = time.Hour + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + + qp := newQueuedMetricsProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + mockP.run(func() { + // This is asynchronous so it should just enqueue, no errors expected. + require.NoError(t, qp.ConsumeMetrics(context.Background(), md)) + }) + mockP.awaitAsyncProcessing() + <-time.After(200 * time.Millisecond) + require.Equal(t, 1, qp.queue.Size()) + + mockP.run(func() { + // The queue is full, cannot enqueue other item + require.Error(t, qp.ConsumeMetrics(context.Background(), md)) + }) + obsreporttest.CheckProcessorMetricsViews(t, cfg.Name(), 4, 4, 0) +} + +func TestTraceQueueProcessorHappyPath(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + views := processor.MetricViews() + assert.NoError(t, view.Register(views...)) + defer view.Unregister(views...) + + mockP := newMockConcurrentSpanProcessor() + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + cfg := createDefaultConfig().(*Config) + qp := newQueuedTracesProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + mockP.stop() + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + wantBatches := 10 + wantSpans := 20 + for i := 0; i < wantBatches; i++ { + td := testdata.GenerateTraceDataTwoSpansSameResource() + mockP.run(func() { + require.NoError(t, qp.ConsumeTraces(context.Background(), td)) + }) + } + + // Wait until all batches received + mockP.awaitAsyncProcessing() + + mockP.checkNumBatches(t, wantBatches) + mockP.checkNumSpans(t, wantSpans) + + droppedView, err := findViewNamed(views, "processor/"+processor.StatDroppedSpanCount.Name()) + require.NoError(t, err) + + data, err := view.RetrieveData(droppedView.Name) + require.NoError(t, err) + require.Len(t, data, 1) + assert.Equal(t, 0.0, data[0].Data.(*view.SumData).Value) + + data, err = view.RetrieveData("processor/" + processor.StatTraceBatchesDroppedCount.Name()) + require.NoError(t, err) + assert.Equal(t, 0.0, data[0].Data.(*view.SumData).Value) + obsreporttest.CheckProcessorTracesViews(t, cfg.Name(), int64(wantSpans), 0, 0) +} + +func TestMetricsQueueProcessorHappyPath(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + mockP := newMockConcurrentSpanProcessor() + creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()} + cfg := createDefaultConfig().(*Config) + qp := newQueuedMetricsProcessor(creationParams, mockP, cfg) + require.NoError(t, qp.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { + assert.NoError(t, qp.Shutdown(context.Background())) + }) + + wantBatches := 10 + wantMetricPoints := 2 * 20 + for i := 0; i < wantBatches; i++ { + md := testdata.GenerateMetricsTwoMetrics() + mockP.run(func() { + require.NoError(t, qp.ConsumeMetrics(context.Background(), md)) + }) + } + + // Wait until all batches received + mockP.awaitAsyncProcessing() + + mockP.checkNumBatches(t, wantBatches) + mockP.checkNumPoints(t, wantMetricPoints) + obsreporttest.CheckProcessorMetricsViews(t, cfg.Name(), int64(wantMetricPoints), 0, 0) +} + +type mockConcurrentSpanProcessor struct { + waitGroup *sync.WaitGroup + mu sync.Mutex + consumeError error + batchCount int64 + spanCount int64 + metricPointsCount int64 + stopped int32 +} + +var _ consumer.TracesConsumer = (*mockConcurrentSpanProcessor)(nil) +var _ consumer.MetricsConsumer = (*mockConcurrentSpanProcessor)(nil) + +func newMockConcurrentSpanProcessor() *mockConcurrentSpanProcessor { + return &mockConcurrentSpanProcessor{waitGroup: new(sync.WaitGroup)} +} + +func (p *mockConcurrentSpanProcessor) ConsumeTraces(_ context.Context, td pdata.Traces) error { + if atomic.LoadInt32(&p.stopped) == 1 { + return nil + } + atomic.AddInt64(&p.batchCount, 1) + atomic.AddInt64(&p.spanCount, int64(td.SpanCount())) + p.mu.Lock() + defer p.mu.Unlock() + defer p.waitGroup.Done() + return p.consumeError +} + +func (p *mockConcurrentSpanProcessor) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + if atomic.LoadInt32(&p.stopped) == 1 { + return nil + } + atomic.AddInt64(&p.batchCount, 1) + _, mpc := md.MetricAndDataPointCount() + atomic.AddInt64(&p.metricPointsCount, int64(mpc)) + p.mu.Lock() + defer p.mu.Unlock() + defer p.waitGroup.Done() + return p.consumeError +} + +func (p *mockConcurrentSpanProcessor) GetCapabilities() component.ProcessorCapabilities { + return component.ProcessorCapabilities{MutatesConsumedData: false} +} + +func (p *mockConcurrentSpanProcessor) checkNumBatches(t *testing.T, want int) { + assert.EqualValues(t, want, atomic.LoadInt64(&p.batchCount)) +} + +func (p *mockConcurrentSpanProcessor) checkNumSpans(t *testing.T, want int) { + assert.EqualValues(t, want, atomic.LoadInt64(&p.spanCount)) +} + +func (p *mockConcurrentSpanProcessor) checkNumPoints(t *testing.T, want int) { + assert.EqualValues(t, want, atomic.LoadInt64(&p.metricPointsCount)) +} + +func (p *mockConcurrentSpanProcessor) updateError(err error) { + p.mu.Lock() + defer p.mu.Unlock() + p.consumeError = err +} + +func (p *mockConcurrentSpanProcessor) run(fn func()) { + p.waitGroup.Add(1) + fn() +} + +func (p *mockConcurrentSpanProcessor) awaitAsyncProcessing() { + p.waitGroup.Wait() +} + +func (p *mockConcurrentSpanProcessor) stop() { + atomic.StoreInt32(&p.stopped, 1) +} + +func findViewNamed(views []*view.View, name string) (*view.View, error) { + for _, v := range views { + if v.Name == name { + return v, nil + } + } + return nil, fmt.Errorf("view %s not found", name) +} diff --git a/internal/otel_collector/processor/queuedprocessor/testdata/config.yaml b/internal/otel_collector/processor/queuedprocessor/testdata/config.yaml new file mode 100644 index 00000000000..095948de2a3 --- /dev/null +++ b/internal/otel_collector/processor/queuedprocessor/testdata/config.yaml @@ -0,0 +1,20 @@ +receivers: + examplereceiver: + +processors: + queued_retry: + queued_retry/2: + num_workers: 2 + queue_size: 10 + retry_on_failure: true + backoff_delay: 5s + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [queued_retry/2] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/resourceprocessor/README.md b/internal/otel_collector/processor/resourceprocessor/README.md new file mode 100644 index 00000000000..222c79fa5ae --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/README.md @@ -0,0 +1,28 @@ +# Resource Processor + +Supported pipeline types: metrics, traces, logs + +The resource processor can be used to apply changes on resource attributes. +Please refer to [config.go](./config.go) for the config spec. + +`attributes` represents actions that can be applied on resource attributes. +See processor/attributesprocessor/README.md for more details on supported attributes actions. + +Examples: + +```yaml +processors: + resource: + attributes: + - key: cloud.zone + value: "zone-1" + action: upsert + - key: k8s.cluster.name + from_attribute: k8s-cluster + action: insert + - key: redundant-attribute + action: delete +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. diff --git a/internal/otel_collector/processor/resourceprocessor/config.go b/internal/otel_collector/processor/resourceprocessor/config.go new file mode 100644 index 00000000000..3085241f7d8 --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/config.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourceprocessor + +import ( + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +// Config defines configuration for Resource processor. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + + // AttributesActions specifies the list of actions to be applied on resource attributes. + // The set of actions are {INSERT, UPDATE, UPSERT, DELETE, HASH, EXTRACT}. + AttributesActions []processorhelper.ActionKeyValue `mapstructure:"attributes"` + + // ResourceType field is deprecated. Set "opencensus.type" key in "attributes.upsert" map instead. + ResourceType string `mapstructure:"type"` + + // Deprecated: Use "attributes.upsert" instead. + Labels map[string]string `mapstructure:"labels"` +} diff --git a/internal/otel_collector/processor/resourceprocessor/config_test.go b/internal/otel_collector/processor/resourceprocessor/config_test.go new file mode 100644 index 00000000000..68dfffd5c3a --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/config_test.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourceprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factories.Processors[typeStr] = NewFactory() + + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + assert.NoError(t, err) + assert.NotNil(t, cfg) + + assert.Equal(t, cfg.Processors["resource"], &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource", + }, + AttributesActions: []processorhelper.ActionKeyValue{ + {Key: "cloud.zone", Value: "zone-1", Action: processorhelper.UPSERT}, + {Key: "k8s.cluster.name", FromAttribute: "k8s-cluster", Action: processorhelper.INSERT}, + {Key: "redundant-attribute", Action: processorhelper.DELETE}, + }, + }) + + assert.Equal(t, cfg.Processors["resource/invalid"], &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource/invalid", + }, + }) +} diff --git a/internal/otel_collector/processor/resourceprocessor/doc.go b/internal/otel_collector/processor/resourceprocessor/doc.go new file mode 100644 index 00000000000..7e350de1f07 --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package resourceprocessor implements a processor for specifying resource +// labels to be added to OpenCensus trace data and metrics data. +package resourceprocessor diff --git a/internal/otel_collector/processor/resourceprocessor/factory.go b/internal/otel_collector/processor/resourceprocessor/factory.go new file mode 100644 index 00000000000..b76ef57398e --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/factory.go @@ -0,0 +1,141 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourceprocessor + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" + "go.opentelemetry.io/collector/translator/conventions" +) + +const ( + // The value of "type" key in configuration. + typeStr = "resource" +) + +var processorCapabilities = component.ProcessorCapabilities{MutatesConsumedData: true} + +// NewFactory returns a new factory for the Resource processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor), + processorhelper.WithMetrics(createMetricsProcessor), + processorhelper.WithLogs(createLogsProcessor)) +} + +// Note: This isn't a valid configuration because the processor would do no work. +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createTraceProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer) (component.TracesProcessor, error) { + attrProc, err := createAttrProcessor(cfg.(*Config), params.Logger) + if err != nil { + return nil, err + } + return processorhelper.NewTraceProcessor( + cfg, + nextConsumer, + &resourceProcessor{attrProc: attrProc}, + processorhelper.WithCapabilities(processorCapabilities)) +} + +func createMetricsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.MetricsConsumer) (component.MetricsProcessor, error) { + attrProc, err := createAttrProcessor(cfg.(*Config), params.Logger) + if err != nil { + return nil, err + } + return processorhelper.NewMetricsProcessor( + cfg, + nextConsumer, + &resourceProcessor{attrProc: attrProc}, + processorhelper.WithCapabilities(processorCapabilities)) +} + +func createLogsProcessor( + _ context.Context, + params component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.LogsConsumer) (component.LogsProcessor, error) { + attrProc, err := createAttrProcessor(cfg.(*Config), params.Logger) + if err != nil { + return nil, err + } + return processorhelper.NewLogsProcessor( + cfg, + nextConsumer, + &resourceProcessor{attrProc: attrProc}, + processorhelper.WithCapabilities(processorCapabilities)) +} + +func createAttrProcessor(cfg *Config, logger *zap.Logger) (*processorhelper.AttrProc, error) { + handleDeprecatedFields(cfg, logger) + if len(cfg.AttributesActions) == 0 { + return nil, fmt.Errorf("error creating \"%q\" processor due to missing required field \"attributes\"", cfg.Name()) + } + attrProc, err := processorhelper.NewAttrProc(&processorhelper.Settings{Actions: cfg.AttributesActions}) + if err != nil { + return nil, fmt.Errorf("error creating \"%q\" processor: %w", cfg.Name(), err) + } + return attrProc, nil +} + +// handleDeprecatedFields converts deprecated ResourceType and Labels fields into Attributes.Upsert +func handleDeprecatedFields(cfg *Config, logger *zap.Logger) { + + // Upsert value from deprecated ResourceType config to resource attributes with "opencensus.resourcetype" key + if cfg.ResourceType != "" { + logger.Warn("[DEPRECATED] \"type\" field is deprecated and will be removed in future release. " + + "Please set the value to \"attributes\" with key=opencensus.resourcetype and action=upsert.") + upsertResourceType := processorhelper.ActionKeyValue{ + Action: processorhelper.UPSERT, + Key: conventions.OCAttributeResourceType, + Value: cfg.ResourceType, + } + cfg.AttributesActions = append(cfg.AttributesActions, upsertResourceType) + } + + // Upsert values from deprecated Labels config to resource attributes + if len(cfg.Labels) > 0 { + logger.Warn("[DEPRECATED] \"labels\" field is deprecated and will be removed in future release. " + + "Please use \"attributes\" field instead.") + for k, v := range cfg.Labels { + action := processorhelper.ActionKeyValue{Action: processorhelper.UPSERT, Key: k, Value: v} + cfg.AttributesActions = append(cfg.AttributesActions, action) + } + } +} diff --git a/internal/otel_collector/processor/resourceprocessor/factory_test.go b/internal/otel_collector/processor/resourceprocessor/factory_test.go new file mode 100644 index 00000000000..8fe08c12f78 --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/factory_test.go @@ -0,0 +1,117 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourceprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NoError(t, configcheck.ValidateConfig(cfg)) + assert.NotNil(t, cfg) +} + +func TestCreateProcessor(t *testing.T) { + factory := NewFactory() + cfg := &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource", + }, + AttributesActions: []processorhelper.ActionKeyValue{ + {Key: "cloud.zone", Value: "zone-1", Action: processorhelper.UPSERT}, + }, + } + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + assert.NoError(t, err) + assert.NotNil(t, tp) + + mp, err := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewMetricsNop()) + assert.NoError(t, err) + assert.NotNil(t, mp) +} + +func TestInvalidEmptyActions(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + _, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + assert.Error(t, err) + + _, err = factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewMetricsNop()) + assert.Error(t, err) +} + +func TestInvalidAttributeActions(t *testing.T) { + factory := NewFactory() + cfg := &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource", + }, + AttributesActions: []processorhelper.ActionKeyValue{ + {Key: "k", Value: "v", Action: "invalid-action"}, + }, + } + + _, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, nil) + assert.Error(t, err) + + _, err = factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, nil) + assert.Error(t, err) +} + +func TestDeprecatedConfig(t *testing.T) { + cfg := &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource", + }, + ResourceType: "host", + Labels: map[string]string{ + "cloud.zone": "zone-1", + }, + } + + handleDeprecatedFields(cfg, zap.NewNop()) + + assert.EqualValues(t, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource", + }, + ResourceType: "host", + Labels: map[string]string{ + "cloud.zone": "zone-1", + }, + AttributesActions: []processorhelper.ActionKeyValue{ + {Key: "opencensus.resourcetype", Value: "host", Action: processorhelper.UPSERT}, + {Key: "cloud.zone", Value: "zone-1", Action: processorhelper.UPSERT}, + }, + }, cfg) +} diff --git a/internal/otel_collector/processor/resourceprocessor/resource_processor.go b/internal/otel_collector/processor/resourceprocessor/resource_processor.go new file mode 100644 index 00000000000..a9595f0b243 --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/resource_processor.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourceprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +type resourceProcessor struct { + attrProc *processorhelper.AttrProc +} + +// ProcessTraces implements the TProcessor interface +func (rp *resourceProcessor) ProcessTraces(_ context.Context, td pdata.Traces) (pdata.Traces, error) { + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + resource := rss.At(i).Resource() + attrs := resource.Attributes() + rp.attrProc.Process(attrs) + } + return td, nil +} + +// ProcessMetrics implements the MProcessor interface +func (rp *resourceProcessor) ProcessMetrics(_ context.Context, md pdata.Metrics) (pdata.Metrics, error) { + rms := md.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + resource := rms.At(i).Resource() + if resource.Attributes().Len() == 0 { + resource.Attributes().InitEmptyWithCapacity(1) + } + rp.attrProc.Process(resource.Attributes()) + } + return md, nil +} + +// ProcessLogs implements the LProcessor interface +func (rp *resourceProcessor) ProcessLogs(_ context.Context, ld pdata.Logs) (pdata.Logs, error) { + rls := ld.ResourceLogs() + for i := 0; i < rls.Len(); i++ { + resource := rls.At(i).Resource() + attrs := resource.Attributes() + rp.attrProc.Process(attrs) + } + return ld, nil +} diff --git a/internal/otel_collector/processor/resourceprocessor/resource_processor_test.go b/internal/otel_collector/processor/resourceprocessor/resource_processor_test.go new file mode 100644 index 00000000000..bc9b14901ec --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/resource_processor_test.go @@ -0,0 +1,247 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourceprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +var ( + processorSettings = configmodels.ProcessorSettings{ + TypeVal: "resource", + NameVal: "resource", + } + + cfg = &Config{ + ProcessorSettings: processorSettings, + AttributesActions: []processorhelper.ActionKeyValue{ + {Key: "cloud.zone", Value: "zone-1", Action: processorhelper.UPSERT}, + {Key: "k8s.cluster.name", FromAttribute: "k8s-cluster", Action: processorhelper.INSERT}, + {Key: "redundant-attribute", Action: processorhelper.DELETE}, + }, + } +) + +func TestResourceProcessorAttributesUpsert(t *testing.T) { + tests := []struct { + name string + config *Config + sourceAttributes map[string]string + wantAttributes map[string]string + }{ + { + name: "config_with_attributes_applied_on_nil_resource", + config: cfg, + sourceAttributes: nil, + wantAttributes: map[string]string{ + "cloud.zone": "zone-1", + }, + }, + { + name: "config_with_attributes_applied_on_empty_resource", + config: cfg, + sourceAttributes: map[string]string{}, + wantAttributes: map[string]string{ + "cloud.zone": "zone-1", + }, + }, + { + name: "config_attributes_applied_on_existing_resource_attributes", + config: cfg, + sourceAttributes: map[string]string{ + "cloud.zone": "to-be-replaced", + "k8s-cluster": "test-cluster", + "redundant-attribute": "to-be-removed", + }, + wantAttributes: map[string]string{ + "cloud.zone": "zone-1", + "k8s-cluster": "test-cluster", + "k8s.cluster.name": "test-cluster", + }, + }, + { + name: "config_attributes_replacement", + config: &Config{ + ProcessorSettings: processorSettings, + AttributesActions: []processorhelper.ActionKeyValue{ + {Key: "k8s.cluster.name", FromAttribute: "k8s-cluster", Action: processorhelper.INSERT}, + {Key: "k8s-cluster", Action: processorhelper.DELETE}, + }, + }, + sourceAttributes: map[string]string{ + "k8s-cluster": "test-cluster", + }, + wantAttributes: map[string]string{ + "k8s.cluster.name": "test-cluster", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test trace consumer + ttn := &testTraceConsumer{} + + factory := NewFactory() + rtp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, tt.config, ttn) + require.NoError(t, err) + assert.True(t, rtp.GetCapabilities().MutatesConsumedData) + + sourceTraceData := generateTraceData(tt.sourceAttributes) + wantTraceData := generateTraceData(tt.wantAttributes) + err = rtp.ConsumeTraces(context.Background(), sourceTraceData) + require.NoError(t, err) + assert.EqualValues(t, wantTraceData, ttn.td) + + // Test metrics consumer + tmn := &testMetricsConsumer{} + rmp, err := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, tt.config, tmn) + require.NoError(t, err) + assert.True(t, rtp.GetCapabilities().MutatesConsumedData) + + sourceMetricData := generateMetricData(tt.sourceAttributes) + wantMetricData := generateMetricData(tt.wantAttributes) + err = rmp.ConsumeMetrics(context.Background(), sourceMetricData) + require.NoError(t, err) + assert.EqualValues(t, wantMetricData, tmn.md) + + // Test logs consumer + tln := &testLogsConsumer{} + rlp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, tt.config, tln) + require.NoError(t, err) + assert.True(t, rtp.GetCapabilities().MutatesConsumedData) + + sourceLogData := generateLogData(tt.sourceAttributes) + wantLogData := generateLogData(tt.wantAttributes) + err = rlp.ConsumeLogs(context.Background(), sourceLogData) + require.NoError(t, err) + assert.EqualValues(t, wantLogData, tln.ld) + }) + } +} + +func TestResourceProcessorError(t *testing.T) { + ttn := &testTraceConsumer{} + + badCfg := &Config{ + ProcessorSettings: processorSettings, + AttributesActions: nil, + } + + factory := NewFactory() + rtp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, badCfg, ttn) + require.Error(t, err) + require.Nil(t, rtp) + + // Test metrics consumer + tmn := &testMetricsConsumer{} + rmp, err := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{}, badCfg, tmn) + require.Error(t, err) + require.Nil(t, rmp) + + // Test logs consumer + tln := &testLogsConsumer{} + rlp, err := factory.CreateLogsProcessor(context.Background(), component.ProcessorCreateParams{}, badCfg, tln) + require.Error(t, err) + require.Nil(t, rlp) +} + +func generateTraceData(attributes map[string]string) pdata.Traces { + td := testdata.GenerateTraceDataOneSpanNoResource() + if attributes == nil { + return td + } + resource := td.ResourceSpans().At(0).Resource() + for k, v := range attributes { + resource.Attributes().InsertString(k, v) + } + resource.Attributes().Sort() + return td +} + +func generateMetricData(attributes map[string]string) pdata.Metrics { + md := testdata.GenerateMetricsOneMetricNoResource() + if attributes == nil { + return md + } + resource := md.ResourceMetrics().At(0).Resource() + for k, v := range attributes { + resource.Attributes().InsertString(k, v) + } + resource.Attributes().Sort() + return md +} + +func generateLogData(attributes map[string]string) pdata.Logs { + ld := testdata.GenerateLogDataOneLogNoResource() + if attributes == nil { + return ld + } + resource := ld.ResourceLogs().At(0).Resource() + for k, v := range attributes { + resource.Attributes().InsertString(k, v) + } + resource.Attributes().Sort() + return ld +} + +type testTraceConsumer struct { + td pdata.Traces +} + +func (ttn *testTraceConsumer) ConsumeTraces(_ context.Context, td pdata.Traces) error { + // sort attributes to be able to compare traces + for i := 0; i < td.ResourceSpans().Len(); i++ { + td.ResourceSpans().At(i).Resource().Attributes().Sort() + } + ttn.td = td + return nil +} + +type testMetricsConsumer struct { + md pdata.Metrics +} + +func (tmn *testMetricsConsumer) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + // sort attributes to be able to compare traces + for i := 0; i < md.ResourceMetrics().Len(); i++ { + md.ResourceMetrics().At(i).Resource().Attributes().Sort() + } + tmn.md = md + return nil +} + +type testLogsConsumer struct { + ld pdata.Logs +} + +func (tln *testLogsConsumer) ConsumeLogs(_ context.Context, ld pdata.Logs) error { + // sort attributes to be able to compare traces + for i := 0; i < ld.ResourceLogs().Len(); i++ { + ld.ResourceLogs().At(i).Resource().Attributes().Sort() + } + tln.ld = ld + return nil +} diff --git a/internal/otel_collector/processor/resourceprocessor/testdata/config.yaml b/internal/otel_collector/processor/resourceprocessor/testdata/config.yaml new file mode 100644 index 00000000000..30cff1fddd8 --- /dev/null +++ b/internal/otel_collector/processor/resourceprocessor/testdata/config.yaml @@ -0,0 +1,41 @@ +receivers: + examplereceiver: + +processors: + # The following specifies a resource configuration doing the changes on resource attributes: + # 1. Set "cloud.zone" attributes with "zone-1" value ignoring existing values. + # 2. Copy "k8s-cluster" attribute value to "k8s.cluster.name" attribute, nothing happens if "k8s-cluster" not found. + # 3. Remove "redundant-attribute" attribute. + # There are many more attribute modification actions supported, + # check processor/attributesprocessor/testdata/config.yaml for reference. + resource: + attributes: + - key: cloud.zone + value: zone-1 + action: upsert + - key: k8s.cluster.name + from_attribute: k8s-cluster + action: insert + - key: redundant-attribute + action: delete + # The following specifies an invalid resource configuration, it has to have at least one action set in attributes field. + resource/invalid: + + +exporters: + exampleexporter: + +service: + pipelines: + logs: + receivers: [examplereceiver] + processors: [resource] + exporters: [exampleexporter] + metrics: + receivers: [examplereceiver] + processors: [resource] + exporters: [exampleexporter] + traces: + receivers: [examplereceiver] + processors: [resource] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/README.md b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/README.md new file mode 100644 index 00000000000..31e593a1fb7 --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/README.md @@ -0,0 +1,33 @@ +# Probabilistic Sampling Processor + +Supported pipeline types: traces + +The probabilistic sampler supports two types of sampling: + +1. `sampling.priority` [semantic +convention](https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table) +as defined by OpenTracing +2. Trace ID hashing + +The `sampling.priority` semantic convention takes priority over trace ID hashing. As the name +implies, trace ID hashing samples based on hash values determined by trace IDs. In order for +trace ID hashing to work, all collectors for a given tier (e.g. behind the same load balancer) +must have the same `hash_seed`. It is also possible to leverage a different `hash_seed` at +different collector tiers to support additional sampling requirements. Please refer to +[config.go](./config.go) for the config spec. + +The following configuration options can be modified: +- `hash_seed` (no default): An integer used to compute the hash algorithm. Note that all collectors for a given tier (e.g. behind the same load balancer) should have the same hash_seed. +- `sampling_percentage` (default = 0): Percentage at which traces are sampled; >= 100 samples all traces + +Examples: + +```yaml +processors: + probabilistic_sampler: + hash_seed: 22 + sampling_percentage: 15.3 +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config.go b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config.go new file mode 100644 index 00000000000..12a2407b0af --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probabilisticsamplerprocessor + +import "go.opentelemetry.io/collector/config/configmodels" + +// Config has the configuration guiding the trace sampler processor. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + // SamplingPercentage is the percentage rate at which traces are going to be sampled. Defaults to zero, i.e.: no sample. + // Values greater or equal 100 are treated as "sample all traces". + SamplingPercentage float32 `mapstructure:"sampling_percentage"` + // HashSeed allows one to configure the hashing seed. This is important in scenarios where multiple layers of collectors + // have different sampling rates: if they use the same seed all passing one layer may pass the other even if they have + // different sampling rates, configuring different seeds avoids that. + HashSeed uint32 `mapstructure:"hash_seed"` +} diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config_test.go b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config_test.go new file mode 100644 index 00000000000..6a09b4bf4f5 --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/config_test.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probabilisticsamplerprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + p0 := cfg.Processors["probabilistic_sampler"] + assert.Equal(t, p0, + &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: "probabilistic_sampler", + NameVal: "probabilistic_sampler", + }, + SamplingPercentage: 15.3, + HashSeed: 22, + }) + +} + +func TestLoadConfigEmpty(t *testing.T) { + factories, err := componenttest.ExampleComponents() + require.NoError(t, err) + factories.Processors, err = component.MakeProcessorFactoryMap(NewFactory()) + require.NotNil(t, factories.Processors) + require.NoError(t, err) + + config, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "empty.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, config) + p0 := config.Processors["probabilistic_sampler"] + assert.Equal(t, p0, createDefaultConfig()) +} diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory.go b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory.go new file mode 100644 index 00000000000..f8af5fc3447 --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probabilisticsamplerprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" trace-samplers in configuration. + typeStr = "probabilistic_sampler" +) + +// NewFactory returns a new factory for the Probabilistic sampler processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor)) +} + +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +// CreateTracesProcessor creates a trace processor based on this config. +func createTraceProcessor( + _ context.Context, + _ component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, +) (component.TracesProcessor, error) { + oCfg := cfg.(*Config) + return newTraceProcessor(nextConsumer, *oCfg) +} diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory_test.go b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory_test.go new file mode 100644 index 00000000000..3f39318e456 --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/factory_test.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probabilisticsamplerprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateProcessor(t *testing.T) { + cfg := createDefaultConfig() + params := component.ProcessorCreateParams{Logger: zap.NewNop()} + tp, err := createTraceProcessor(context.Background(), params, cfg, consumertest.NewTracesNop()) + assert.NotNil(t, tp) + assert.NoError(t, err, "cannot create trace processor") +} diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler.go b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler.go new file mode 100644 index 00000000000..2f97912fe77 --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler.go @@ -0,0 +1,234 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probabilisticsamplerprocessor + +import ( + "context" + "strconv" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// samplingPriority has the semantic result of parsing the "sampling.priority" +// attribute per OpenTracing semantic conventions. +type samplingPriority int + +const ( + // deferDecision means that the decision if a span will be "sampled" (ie.: + // forwarded by the collector) is made by hashing the trace ID according + // to the configured sampling rate. + deferDecision samplingPriority = iota + // mustSampleSpan indicates that the span had a "sampling.priority" attribute + // greater than zero and it is going to be sampled, ie.: forwarded by the + // collector. + mustSampleSpan + // doNotSampleSpan indicates that the span had a "sampling.priority" attribute + // equal zero and it is NOT going to be sampled, ie.: it won't be forwarded + // by the collector. + doNotSampleSpan + + // The constants help translate user friendly percentages to numbers direct used in sampling. + numHashBuckets = 0x4000 // Using a power of 2 to avoid division. + bitMaskHashBuckets = numHashBuckets - 1 + percentageScaleFactor = numHashBuckets / 100.0 +) + +type tracesamplerprocessor struct { + nextConsumer consumer.TracesConsumer + scaledSamplingRate uint32 + hashSeed uint32 +} + +// newTraceProcessor returns a processor.TracesProcessor that will perform head sampling according to the given +// configuration. +func newTraceProcessor(nextConsumer consumer.TracesConsumer, cfg Config) (component.TracesProcessor, error) { + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + return &tracesamplerprocessor{ + nextConsumer: nextConsumer, + // Adjust sampling percentage on private so recalculations are avoided. + scaledSamplingRate: uint32(cfg.SamplingPercentage * percentageScaleFactor), + hashSeed: cfg.HashSeed, + }, nil +} + +func (tsp *tracesamplerprocessor) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + rspans := td.ResourceSpans() + sampledTraceData := pdata.NewTraces() + for i := 0; i < rspans.Len(); i++ { + tsp.processTraces(rspans.At(i), sampledTraceData) + } + return tsp.nextConsumer.ConsumeTraces(ctx, sampledTraceData) +} + +func (tsp *tracesamplerprocessor) processTraces(resourceSpans pdata.ResourceSpans, sampledTraceData pdata.Traces) { + scaledSamplingRate := tsp.scaledSamplingRate + + sampledTraceData.ResourceSpans().Resize(sampledTraceData.ResourceSpans().Len() + 1) + rs := sampledTraceData.ResourceSpans().At(sampledTraceData.ResourceSpans().Len() - 1) + resourceSpans.Resource().CopyTo(rs.Resource()) + rs.InstrumentationLibrarySpans().Resize(1) + spns := rs.InstrumentationLibrarySpans().At(0).Spans() + + ilss := resourceSpans.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + for k := 0; k < ils.Spans().Len(); k++ { + span := ils.Spans().At(k) + sp := parseSpanSamplingPriority(span) + if sp == doNotSampleSpan { + // The OpenTelemetry mentions this as a "hint" we take a stronger + // approach and do not sample the span since some may use it to + // remove specific spans from traces. + continue + } + + // If one assumes random trace ids hashing may seems avoidable, however, traces can be coming from sources + // with various different criteria to generate trace id and perhaps were already sampled without hashing. + // Hashing here prevents bias due to such systems. + tidBytes := span.TraceID().Bytes() + sampled := sp == mustSampleSpan || + hash(tidBytes[:], tsp.hashSeed)&bitMaskHashBuckets < scaledSamplingRate + + if sampled { + spns.Append(span) + } + } + } +} + +func (tsp *tracesamplerprocessor) GetCapabilities() component.ProcessorCapabilities { + return component.ProcessorCapabilities{MutatesConsumedData: false} +} + +// Start is invoked during service startup. +func (tsp *tracesamplerprocessor) Start(context.Context, component.Host) error { + return nil +} + +// Shutdown is invoked during service shutdown. +func (tsp *tracesamplerprocessor) Shutdown(context.Context) error { + return nil +} + +// parseSpanSamplingPriority checks if the span has the "sampling.priority" tag to +// decide if the span should be sampled or not. The usage of the tag follows the +// OpenTracing semantic tags: +// https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table +func parseSpanSamplingPriority(span pdata.Span) samplingPriority { + attribMap := span.Attributes() + if attribMap.Len() <= 0 { + return deferDecision + } + + samplingPriorityAttrib, ok := attribMap.Get("sampling.priority") + if !ok { + return deferDecision + } + + // By default defer the decision. + decision := deferDecision + + // Try check for different types since there are various client libraries + // using different conventions regarding "sampling.priority". Besides the + // client libraries it is also possible that the type was lost in translation + // between different formats. + switch samplingPriorityAttrib.Type() { + case pdata.AttributeValueINT: + value := samplingPriorityAttrib.IntVal() + if value == 0 { + decision = doNotSampleSpan + } else if value > 0 { + decision = mustSampleSpan + } + case pdata.AttributeValueDOUBLE: + value := samplingPriorityAttrib.DoubleVal() + if value == 0.0 { + decision = doNotSampleSpan + } else if value > 0.0 { + decision = mustSampleSpan + } + case pdata.AttributeValueSTRING: + attribVal := samplingPriorityAttrib.StringVal() + if value, err := strconv.ParseFloat(attribVal, 64); err == nil { + if value == 0.0 { + decision = doNotSampleSpan + } else if value > 0.0 { + decision = mustSampleSpan + } + } + } + + return decision +} + +// hash is a murmur3 hash function, see http://en.wikipedia.org/wiki/MurmurHash +func hash(key []byte, seed uint32) (hash uint32) { + const ( + c1 = 0xcc9e2d51 + c2 = 0x1b873593 + c3 = 0x85ebca6b + c4 = 0xc2b2ae35 + r1 = 15 + r2 = 13 + m = 5 + n = 0xe6546b64 + ) + + hash = seed + iByte := 0 + for ; iByte+4 <= len(key); iByte += 4 { + k := uint32(key[iByte]) | uint32(key[iByte+1])<<8 | uint32(key[iByte+2])<<16 | uint32(key[iByte+3])<<24 + k *= c1 + k = (k << r1) | (k >> (32 - r1)) + k *= c2 + hash ^= k + hash = (hash << r2) | (hash >> (32 - r2)) + hash = hash*m + n + } + + // TraceId and SpanId have lengths that are multiple of 4 so the code below is never expected to + // be hit when sampling traces. However, it is preserved here to keep it as a correct murmur3 implementation. + // This is enforced via tests. + var remainingBytes uint32 + switch len(key) - iByte { + case 3: + remainingBytes += uint32(key[iByte+2]) << 16 + fallthrough + case 2: + remainingBytes += uint32(key[iByte+1]) << 8 + fallthrough + case 1: + remainingBytes += uint32(key[iByte]) + remainingBytes *= c1 + remainingBytes = (remainingBytes << r1) | (remainingBytes >> (32 - r1)) + remainingBytes *= c2 + hash ^= remainingBytes + } + + hash ^= uint32(len(key)) + hash ^= hash >> 16 + hash *= c3 + hash ^= hash >> 13 + hash *= c4 + hash ^= hash >> 16 + + return +} diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler_test.go b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler_test.go new file mode 100644 index 00000000000..e84ac94249d --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/probabilisticsampler_test.go @@ -0,0 +1,506 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probabilisticsamplerprocessor + +import ( + "context" + "math" + "math/rand" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestNewTraceProcessor(t *testing.T) { + tests := []struct { + name string + nextConsumer consumer.TracesConsumer + cfg Config + want component.TracesProcessor + wantErr bool + }{ + { + name: "nil_nextConsumer", + wantErr: true, + }, + { + name: "happy_path", + nextConsumer: consumertest.NewTracesNop(), + cfg: Config{ + SamplingPercentage: 15.5, + }, + want: &tracesamplerprocessor{ + nextConsumer: consumertest.NewTracesNop(), + }, + }, + { + name: "happy_path_hash_seed", + nextConsumer: consumertest.NewTracesNop(), + cfg: Config{ + SamplingPercentage: 13.33, + HashSeed: 4321, + }, + want: &tracesamplerprocessor{ + nextConsumer: consumertest.NewTracesNop(), + hashSeed: 4321, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.wantErr { + // The truncation below with uint32 cannot be defined at initialization (compiler error), performing it at runtime. + tt.want.(*tracesamplerprocessor).scaledSamplingRate = uint32(tt.cfg.SamplingPercentage * percentageScaleFactor) + } + got, err := newTraceProcessor(tt.nextConsumer, tt.cfg) + if (err != nil) != tt.wantErr { + t.Errorf("newTraceProcessor() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("newTraceProcessor() = %v, want %v", got, tt.want) + } + }) + } +} + +// Test_tracesamplerprocessor_SamplingPercentageRange checks for different sampling rates and ensures +// that they are within acceptable deltas. +func Test_tracesamplerprocessor_SamplingPercentageRange(t *testing.T) { + tests := []struct { + name string + cfg Config + numBatches int + numTracesPerBatch int + acceptableDelta float64 + }{ + { + name: "random_sampling_tiny", + cfg: Config{ + SamplingPercentage: 0.03, + }, + numBatches: 1e5, + numTracesPerBatch: 2, + acceptableDelta: 0.01, + }, + { + name: "random_sampling_small", + cfg: Config{ + SamplingPercentage: 5, + }, + numBatches: 1e5, + numTracesPerBatch: 2, + acceptableDelta: 0.01, + }, + { + name: "random_sampling_medium", + cfg: Config{ + SamplingPercentage: 50.0, + }, + numBatches: 1e5, + numTracesPerBatch: 4, + acceptableDelta: 0.1, + }, + { + name: "random_sampling_high", + cfg: Config{ + SamplingPercentage: 90.0, + }, + numBatches: 1e5, + numTracesPerBatch: 1, + acceptableDelta: 0.2, + }, + { + name: "random_sampling_all", + cfg: Config{ + SamplingPercentage: 100.0, + }, + numBatches: 1e5, + numTracesPerBatch: 1, + acceptableDelta: 0.0, + }, + } + const testSvcName = "test-svc" + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.TracesSink) + tsp, err := newTraceProcessor(sink, tt.cfg) + if err != nil { + t.Errorf("error when creating tracesamplerprocessor: %v", err) + return + } + for _, td := range genRandomTestData(tt.numBatches, tt.numTracesPerBatch, testSvcName, 1) { + if err := tsp.ConsumeTraces(context.Background(), td); err != nil { + t.Errorf("tracesamplerprocessor.ConsumeTraceData() error = %v", err) + return + } + } + _, sampled := assertSampledData(t, sink.AllTraces(), testSvcName) + actualPercentageSamplingPercentage := float32(sampled) / float32(tt.numBatches*tt.numTracesPerBatch) * 100.0 + delta := math.Abs(float64(actualPercentageSamplingPercentage - tt.cfg.SamplingPercentage)) + if delta > tt.acceptableDelta { + t.Errorf( + "got %f percentage sampling rate, want %f (allowed delta is %f but got %f)", + actualPercentageSamplingPercentage, + tt.cfg.SamplingPercentage, + tt.acceptableDelta, + delta, + ) + } + }) + } +} + +// Test_tracesamplerprocessor_SamplingPercentageRange_MultipleResourceSpans checks for number of spans sent to xt consumer. This is to avoid duplicate spans +func Test_tracesamplerprocessor_SamplingPercentageRange_MultipleResourceSpans(t *testing.T) { + tests := []struct { + name string + cfg Config + numBatches int + numTracesPerBatch int + acceptableDelta float64 + resourceSpanPerTrace int + }{ + { + name: "single_batch_single_trace_two_resource_spans", + cfg: Config{ + SamplingPercentage: 100.0, + }, + numBatches: 1, + numTracesPerBatch: 1, + acceptableDelta: 0.0, + resourceSpanPerTrace: 2, + }, + { + name: "single_batch_two_traces_two_resource_spans", + cfg: Config{ + SamplingPercentage: 100.0, + }, + numBatches: 1, + numTracesPerBatch: 2, + acceptableDelta: 0.0, + resourceSpanPerTrace: 2, + }, + } + const testSvcName = "test-svc" + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.TracesSink) + tsp, err := newTraceProcessor(sink, tt.cfg) + if err != nil { + t.Errorf("error when creating tracesamplerprocessor: %v", err) + return + } + + for _, td := range genRandomTestData(tt.numBatches, tt.numTracesPerBatch, testSvcName, tt.resourceSpanPerTrace) { + if err := tsp.ConsumeTraces(context.Background(), td); err != nil { + t.Errorf("tracesamplerprocessor.ConsumeTraceData() error = %v", err) + return + } + assert.Equal(t, tt.resourceSpanPerTrace*tt.numTracesPerBatch, sink.SpansCount()) + sink.Reset() + } + + }) + } +} + +// Test_tracesamplerprocessor_SpanSamplingPriority checks if handling of "sampling.priority" is correct. +func Test_tracesamplerprocessor_SpanSamplingPriority(t *testing.T) { + singleSpanWithAttrib := func(key string, attribValue pdata.AttributeValue) pdata.Traces { + traces := pdata.NewTraces() + traces.ResourceSpans().Resize(1) + rs := traces.ResourceSpans().At(0) + rs.InstrumentationLibrarySpans().Resize(1) + instrLibrarySpans := rs.InstrumentationLibrarySpans().At(0) + instrLibrarySpans.Spans().Append(getSpanWithAttributes(key, attribValue)) + return traces + } + tests := []struct { + name string + cfg Config + td pdata.Traces + sampled bool + }{ + { + name: "must_sample", + cfg: Config{ + SamplingPercentage: 0.0, + }, + td: singleSpanWithAttrib( + "sampling.priority", + pdata.NewAttributeValueInt(2)), + sampled: true, + }, + { + name: "must_sample_double", + cfg: Config{ + SamplingPercentage: 0.0, + }, + td: singleSpanWithAttrib( + "sampling.priority", + pdata.NewAttributeValueDouble(1)), + sampled: true, + }, + { + name: "must_sample_string", + cfg: Config{ + SamplingPercentage: 0.0, + }, + td: singleSpanWithAttrib( + "sampling.priority", + pdata.NewAttributeValueString("1")), + sampled: true, + }, + { + name: "must_not_sample", + cfg: Config{ + SamplingPercentage: 100.0, + }, + td: singleSpanWithAttrib( + "sampling.priority", + pdata.NewAttributeValueInt(0)), + }, + { + name: "must_not_sample_double", + cfg: Config{ + SamplingPercentage: 100.0, + }, + td: singleSpanWithAttrib( + "sampling.priority", + pdata.NewAttributeValueDouble(0)), + }, + { + name: "must_not_sample_string", + cfg: Config{ + SamplingPercentage: 100.0, + }, + td: singleSpanWithAttrib( + "sampling.priority", + pdata.NewAttributeValueString("0")), + }, + { + name: "defer_sample_expect_not_sampled", + cfg: Config{ + SamplingPercentage: 0.0, + }, + td: singleSpanWithAttrib( + "no.sampling.priority", + pdata.NewAttributeValueInt(2)), + }, + { + name: "defer_sample_expect_sampled", + cfg: Config{ + SamplingPercentage: 100.0, + }, + td: singleSpanWithAttrib( + "no.sampling.priority", + pdata.NewAttributeValueInt(2)), + sampled: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.TracesSink) + tsp, err := newTraceProcessor(sink, tt.cfg) + require.NoError(t, err) + + err = tsp.ConsumeTraces(context.Background(), tt.td) + require.NoError(t, err) + + sampledData := sink.AllTraces() + require.Equal(t, 1, len(sampledData)) + assert.Equal(t, tt.sampled, sink.SpansCount() == 1) + }) + } +} + +// Test_parseSpanSamplingPriority ensures that the function parsing the attributes is taking "sampling.priority" +// attribute correctly. +func Test_parseSpanSamplingPriority(t *testing.T) { + tests := []struct { + name string + span pdata.Span + want samplingPriority + }{ + { + name: "nil_span", + span: pdata.NewSpan(), + want: deferDecision, + }, + { + name: "nil_attributes", + span: pdata.NewSpan(), + want: deferDecision, + }, + { + name: "no_sampling_priority", + span: getSpanWithAttributes("key", pdata.NewAttributeValueBool(true)), + want: deferDecision, + }, + { + name: "sampling_priority_int_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueInt(0)), + want: doNotSampleSpan, + }, + { + name: "sampling_priority_int_gt_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueInt(1)), + want: mustSampleSpan, + }, + { + name: "sampling_priority_int_lt_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueInt(-1)), + want: deferDecision, + }, + { + name: "sampling_priority_double_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueDouble(0)), + want: doNotSampleSpan, + }, + { + name: "sampling_priority_double_gt_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueDouble(1)), + want: mustSampleSpan, + }, + { + name: "sampling_priority_double_lt_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueDouble(-1)), + want: deferDecision, + }, + { + name: "sampling_priority_string_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueString("0.0")), + want: doNotSampleSpan, + }, + { + name: "sampling_priority_string_gt_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueString("0.5")), + want: mustSampleSpan, + }, + { + name: "sampling_priority_string_lt_zero", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueString("-0.5")), + want: deferDecision, + }, + { + name: "sampling_priority_string_NaN", + span: getSpanWithAttributes("sampling.priority", pdata.NewAttributeValueString("NaN")), + want: deferDecision, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, parseSpanSamplingPriority(tt.span)) + }) + } +} + +func getSpanWithAttributes(key string, value pdata.AttributeValue) pdata.Span { + span := pdata.NewSpan() + span.SetName("spanName") + span.Attributes().InitFromMap(map[string]pdata.AttributeValue{key: value}) + return span +} + +// Test_hash ensures that the hash function supports different key lengths even if in +// practice it is only expected to receive keys with length 16 (trace id length in OC proto). +func Test_hash(t *testing.T) { + // Statistically a random selection of such small number of keys should not result in + // collisions, but, of course it is possible that they happen, a different random source + // should avoid that. + r := rand.New(rand.NewSource(1)) + fullKey := tracetranslator.UInt64ToByteTraceID(r.Uint64(), r.Uint64()) + seen := make(map[uint32]bool) + for i := 1; i <= len(fullKey); i++ { + key := fullKey[:i] + hash := hash(key, 1) + require.False(t, seen[hash], "Unexpected duplicated hash") + seen[hash] = true + } +} + +// genRandomTestData generates a slice of consumerdata.TraceData with the numBatches elements which one with +// numTracesPerBatch spans (ie.: each span has a different trace ID). All spans belong to the specified +// serviceName. +func genRandomTestData(numBatches, numTracesPerBatch int, serviceName string, resourceSpanCount int) (tdd []pdata.Traces) { + r := rand.New(rand.NewSource(1)) + var traceBatches []pdata.Traces + for i := 0; i < numBatches; i++ { + traces := pdata.NewTraces() + traces.ResourceSpans().Resize(resourceSpanCount) + for j := 0; j < resourceSpanCount; j++ { + rs := traces.ResourceSpans().At(j) + rs.Resource().Attributes().InsertString("service.name", serviceName) + rs.Resource().Attributes().InsertBool("bool", true) + rs.Resource().Attributes().InsertString("string", "yes") + rs.Resource().Attributes().InsertInt("int64", 10000000) + rs.InstrumentationLibrarySpans().Resize(1) + ils := rs.InstrumentationLibrarySpans().At(0) + ils.Spans().Resize(numTracesPerBatch) + + for k := 0; k < numTracesPerBatch; k++ { + span := ils.Spans().At(k) + span.SetTraceID(tracetranslator.UInt64ToTraceID(r.Uint64(), r.Uint64())) + span.SetSpanID(tracetranslator.UInt64ToSpanID(r.Uint64())) + attributes := make(map[string]pdata.AttributeValue) + attributes[tracetranslator.TagHTTPStatusCode] = pdata.NewAttributeValueInt(404) + attributes[tracetranslator.TagHTTPStatusMsg] = pdata.NewAttributeValueString("Not Found") + span.Attributes().InitFromMap(attributes) + } + } + traceBatches = append(traceBatches, traces) + } + + return traceBatches +} + +// assertSampledData checks for no repeated traceIDs and counts the number of spans on the sampled data for +// the given service. +func assertSampledData(t *testing.T, sampled []pdata.Traces, serviceName string) (traceIDs map[[16]byte]bool, spanCount int) { + traceIDs = make(map[[16]byte]bool) + for _, td := range sampled { + rspans := td.ResourceSpans() + for i := 0; i < rspans.Len(); i++ { + rspan := rspans.At(i) + ilss := rspan.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + if svcNameAttr, _ := rspan.Resource().Attributes().Get("service.name"); svcNameAttr.StringVal() != serviceName { + continue + } + for k := 0; k < ils.Spans().Len(); k++ { + spanCount++ + span := ils.Spans().At(k) + key := span.TraceID().Bytes() + if traceIDs[key] { + t.Errorf("same traceID used more than once %q", key) + return + } + traceIDs[key] = true + } + } + } + } + return +} diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/config.yaml b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/config.yaml new file mode 100644 index 00000000000..4ab26d911d0 --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/config.yaml @@ -0,0 +1,35 @@ +receivers: + examplereceiver: + +processors: + # The probabilistic_sampler sets trace sampling by hashing the trace id of + # each span and making the sampling decision based on the hashed value. It + # also implements the "sampling.priority" semantic convention as defined by + # OpenTracing. See + # https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table + # The "sampling.priority" semantics have priority over trace id hashing and + # can be used to control if given spans are sampled, ie.: forwarded, or not. + probabilistic_sampler: + # the percentage rate at which traces are going to be sampled. Defaults to + # zero, i.e.: no sample. Values greater or equal 100 are treated as + # "sample all traces". + sampling_percentage: 15.3 + # hash_seed allows one to configure the hashing seed. This is important in + # scenarios where multiple layers of collectors are used to achieve the + # desired sampling rate, eg.: 10% on first layer and 10% on the + # second, resulting in an overall sampling rate of 1% (10% x 10%). + # If all layers use the same seed, all data passing one layer will also pass + # the next one, independent of the configured sampling rate. Having different + # seeds at different layers ensures that sampling rate in each layer work as + # intended. + hash_seed: 22 + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [probabilistic_sampler] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/empty.yaml b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/empty.yaml new file mode 100644 index 00000000000..0123346ee2f --- /dev/null +++ b/internal/otel_collector/processor/samplingprocessor/probabilisticsamplerprocessor/testdata/empty.yaml @@ -0,0 +1,16 @@ +receivers: + examplereceiver: + +processors: + probabilistic_sampler: + + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [probabilistic_sampler] + exporters: [exampleexporter] diff --git a/internal/otel_collector/processor/spanprocessor/README.md b/internal/otel_collector/processor/spanprocessor/README.md new file mode 100644 index 00000000000..21fceacb579 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/README.md @@ -0,0 +1,101 @@ +# Span Processor + +Supported pipeline types: traces + +The span processor modifies either the span name or attributes of a span based +on the span name. Please refer to +[config.go](./config.go) for the config spec. + +It optionally supports the ability to [include/exclude spans](../README.md#includeexclude-spans). + +The following actions are supported: + +- `name`: Modify the name of attributes within a span + +### Name a span + +The following settings are required: + +- `from_attributes`: The attribute value for the keys are used to create a +new name in the order specified in the configuration. + +The following settings can be optionally configured: + +- `separator`: A string, which is specified will be used to split values + +Note: If renaming is dependent on attributes being modified by the `attributes` +processor, ensure the `span` processor is specified after the `attributes` +processor in the `pipeline` specification. + +```yaml +span: + name: + # from_attributes represents the attribute keys to pull the values from to generate the + # new span name. + from_attributes: [, , ...] + # Separator is the string used to concatenate various parts of the span name. + separator: +``` + +Example: + +```yaml +span: + name: + from_attributes: ["db.svc", "operation"] + separator: "::" +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. + +### Extract attributes from span name + +Takes a list of regular expressions to match span name against and extract +attributes from it based on subexpressions. Must be specified under the +`to_attributes` section. + +The following settings are required: + +- `rules`: A list of rules to extract attribute values from span name. The values +in the span name are replaced by extracted attribute names. Each rule in the list +is regex pattern string. Span name is checked against the regex and if the regex +matches then all named subexpressions of the regex are extracted as attributes +and are added to the span. Each subexpression name becomes an attribute name and +subexpression matched portion becomes the attribute value. The matched portion +in the span name is replaced by extracted attribute name. If the attributes +already exist in the span then they will be overwritten. The process is repeated +for all rules in the order they are specified. Each subsequent rule works on the +span name that is the output after processing the previous rule. +- `break_after_match` (default = false): specifies if processing of rules should stop after the first +match. If it is false rule processing will continue to be performed over the +modified span name. + +```yaml +span/to_attributes: + name: + to_attributes: + rules: + - regexp-rule1 + - regexp-rule2 + - regexp-rule3 + ... + break_after_match: + +``` + +Example: + +```yaml +# Let's assume input span name is /api/v1/document/12345678/update +# Applying the following results in output span name /api/v1/document/{documentId}/update +# and will add a new attribute "documentId"="12345678" to the span. +span/to_attributes: + name: + to_attributes: + rules: + - ^\/api\/v1\/document\/(?P.*)\/update$ +``` + +Refer to [config.yaml](./testdata/config.yaml) for detailed +examples on using the processor. diff --git a/internal/otel_collector/processor/spanprocessor/config.go b/internal/otel_collector/processor/spanprocessor/config.go new file mode 100644 index 00000000000..eadd9f6f904 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/config.go @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanprocessor + +import ( + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/internal/processor/filterconfig" +) + +// Config is the configuration for the span processor. +// Prior to any actions being applied, each span is compared against +// the include properties and then the exclude properties if they are specified. +// This determines if a span is to be processed or not. +type Config struct { + configmodels.ProcessorSettings `mapstructure:",squash"` + + filterconfig.MatchConfig `mapstructure:",squash"` + + // Rename specifies the components required to re-name a span. + // The `from_attributes` field needs to be set for this processor to be properly + // configured. + // Note: The field name is `Rename` to avoid collision with the Name() method + // from configmodels.ProcessorSettings.NamedEntity + Rename Name `mapstructure:"name"` +} + +// Name specifies the attributes to use to re-name a span. +type Name struct { + // Specifies transformations of span name to and from attributes. + // First FromAttributes rules are applied, then ToAttributes are applied. + // At least one of these 2 fields must be set. + + // FromAttributes represents the attribute keys to pull the values from to + // generate the new span name. All attribute keys are required in the span + // to re-name a span. If any attribute is missing from the span, no re-name + // will occur. + // Note: The new span name is constructed in order of the `from_attributes` + // specified in the configuration. This field is required and cannot be empty. + FromAttributes []string `mapstructure:"from_attributes"` + + // Separator is the string used to separate attributes values in the new + // span name. If no value is set, no separator is used between attribute + // values. Used with FromAttributes only. + Separator string `mapstructure:"separator"` + + // ToAttributes specifies a configuration to extract attributes from span name. + ToAttributes *ToAttributes `mapstructure:"to_attributes"` +} + +type ToAttributes struct { + // Rules is a list of rules to extract attribute values from span name. The values + // in the span name are replaced by extracted attribute names. Each rule in the list + // is a regex pattern string. Span name is checked against the regex. If it matches + // then all named subexpressions of the regex are extracted as attributes + // and are added to the span. Each subexpression name becomes an attribute name and + // subexpression matched portion becomes the attribute value. The matched portion + // in the span name is replaced by extracted attribute name. If the attributes + // already exist in the span then they will be overwritten. The process is repeated + // for all rules in the order they are specified. Each subsequent rule works on the + // span name that is the output after processing the previous rule. + Rules []string `mapstructure:"rules"` + + // BreakAfterMatch specifies if processing of rules should stop after the first + // match. If it is false rule processing will continue to be performed over the + // modified span name. + BreakAfterMatch bool `mapstructure:"break_after_match"` +} diff --git a/internal/otel_collector/processor/spanprocessor/config_test.go b/internal/otel_collector/processor/spanprocessor/config_test.go new file mode 100644 index 00000000000..b0fe7cd6566 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/config_test.go @@ -0,0 +1,108 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + assert.NoError(t, err) + assert.NotNil(t, cfg) + + p0 := cfg.Processors["span/custom"] + assert.Equal(t, p0, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: "span/custom", + }, + Rename: Name{ + FromAttributes: []string{"db.svc", "operation", "id"}, + Separator: "::", + }, + }) + + p1 := cfg.Processors["span/no-separator"] + assert.Equal(t, p1, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: "span/no-separator", + }, + Rename: Name{ + FromAttributes: []string{"db.svc", "operation", "id"}, + Separator: "", + }, + }) + + p2 := cfg.Processors["span/to_attributes"] + assert.Equal(t, p2, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: "span/to_attributes", + }, + Rename: Name{ + ToAttributes: &ToAttributes{ + Rules: []string{`^\/api\/v1\/document\/(?P.*)\/update$`}, + }, + }, + }) + + p3 := cfg.Processors["span/includeexclude"] + assert.Equal(t, p3, &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: "span/includeexclude", + }, + MatchConfig: filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createMatchConfig(filterset.Regexp), + Services: []string{`banks`}, + SpanNames: []string{"^(.*?)/(.*?)$"}, + }, + Exclude: &filterconfig.MatchProperties{ + Config: *createMatchConfig(filterset.Strict), + SpanNames: []string{`donot/change`}, + }, + }, + Rename: Name{ + ToAttributes: &ToAttributes{ + Rules: []string{`(?P.*?)$`}, + }, + }, + }) +} + +func createMatchConfig(matchType filterset.MatchType) *filterset.Config { + return &filterset.Config{ + MatchType: matchType, + } +} diff --git a/internal/otel_collector/processor/spanprocessor/doc.go b/internal/otel_collector/processor/spanprocessor/doc.go new file mode 100644 index 00000000000..2086444e613 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package spanprocessor contains logic to modify top level settings of a span, such +// as its name. +package spanprocessor diff --git a/internal/otel_collector/processor/spanprocessor/factory.go b/internal/otel_collector/processor/spanprocessor/factory.go new file mode 100644 index 00000000000..0d835953216 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/factory.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanprocessor + +import ( + "context" + "errors" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // typeStr is the value of "type" Span processor in the configuration. + typeStr = "span" +) + +var processorCapabilities = component.ProcessorCapabilities{MutatesConsumedData: true} + +// errMissingRequiredField is returned when a required field in the config +// is not specified. +// TODO https://github.com/open-telemetry/opentelemetry-collector/issues/215 +// Move this to the error package that allows for span name and field to be specified. +var errMissingRequiredField = errors.New("error creating \"span\" processor: either \"from_attributes\" or \"to_attributes\" must be specified in \"name:\"") + +// NewFactory returns a new factory for the Span processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor)) +} + +func createDefaultConfig() configmodels.Processor { + return &Config{ + ProcessorSettings: configmodels.ProcessorSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createTraceProcessor( + _ context.Context, + _ component.ProcessorCreateParams, + cfg configmodels.Processor, + nextConsumer consumer.TracesConsumer, +) (component.TracesProcessor, error) { + + // 'from_attributes' or 'to_attributes' under 'name' has to be set for the span + // processor to be valid. If not set and not enforced, the processor would do no work. + oCfg := cfg.(*Config) + if len(oCfg.Rename.FromAttributes) == 0 && + (oCfg.Rename.ToAttributes == nil || len(oCfg.Rename.ToAttributes.Rules) == 0) { + return nil, errMissingRequiredField + } + + sp, err := newSpanProcessor(*oCfg) + if err != nil { + return nil, err + } + return processorhelper.NewTraceProcessor( + cfg, + nextConsumer, + sp, + processorhelper.WithCapabilities(processorCapabilities)) +} diff --git a/internal/otel_collector/processor/spanprocessor/factory_test.go b/internal/otel_collector/processor/spanprocessor/factory_test.go new file mode 100644 index 00000000000..692795dd359 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/factory_test.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanprocessor + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestFactory_Type(t *testing.T) { + factory := NewFactory() + assert.Equal(t, factory.Type(), configmodels.Type(typeStr)) +} + +func TestFactory_CreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NoError(t, configcheck.ValidateConfig(cfg)) + + // Check the values of the default configuration. + assert.NotNil(t, cfg) + assert.Equal(t, configmodels.Type(typeStr), cfg.Type()) + assert.Equal(t, typeStr, cfg.Name()) +} + +func TestFactory_CreateTraceProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + + // Name.FromAttributes field needs to be set for the configuration to be valid. + oCfg.Rename.FromAttributes = []string{"test-key"} + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + + require.Nil(t, err) + assert.NotNil(t, tp) +} + +// TestFactory_CreateTraceProcessor_InvalidConfig ensures the default configuration +// returns an error. +func TestFactory_CreateTraceProcessor_InvalidConfig(t *testing.T) { + factory := NewFactory() + + testcases := []struct { + name string + cfg Name + err error + }{ + { + name: "missing_config", + err: errMissingRequiredField, + }, + + { + name: "invalid_regexp", + cfg: Name{ + ToAttributes: &ToAttributes{ + Rules: []string{"\\"}, + }, + }, + err: fmt.Errorf("invalid regexp pattern \\"), + }, + } + + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + cfg := factory.CreateDefaultConfig().(*Config) + cfg.Rename = test.cfg + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewTracesNop()) + require.Nil(t, tp) + assert.EqualValues(t, err, test.err) + }) + } +} + +func TestFactory_CreateMetricProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + mp, err := factory.CreateMetricsProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, cfg, nil) + require.Nil(t, mp) + assert.Equal(t, err, configerror.ErrDataTypeIsNotSupported) +} diff --git a/internal/otel_collector/processor/spanprocessor/span.go b/internal/otel_collector/processor/spanprocessor/span.go new file mode 100644 index 00000000000..340b4211934 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/span.go @@ -0,0 +1,221 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanprocessor + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterspan" +) + +type spanProcessor struct { + config Config + toAttributeRules []toAttributeRule + include filterspan.Matcher + exclude filterspan.Matcher +} + +// toAttributeRule is the compiled equivalent of config.ToAttributes field. +type toAttributeRule struct { + // Compiled regexp. + re *regexp.Regexp + + // Attribute names extracted from the regexp's subexpressions. + attrNames []string +} + +// newSpanProcessor returns the span processor. +func newSpanProcessor(config Config) (*spanProcessor, error) { + include, err := filterspan.NewMatcher(config.Include) + if err != nil { + return nil, err + } + exclude, err := filterspan.NewMatcher(config.Exclude) + if err != nil { + return nil, err + } + + sp := &spanProcessor{ + config: config, + include: include, + exclude: exclude, + } + + // Compile ToAttributes regexp and extract attributes names. + if config.Rename.ToAttributes != nil { + for _, pattern := range config.Rename.ToAttributes.Rules { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, fmt.Errorf("invalid regexp pattern %s", pattern) + } + + rule := toAttributeRule{ + re: re, + // Subexpression names will become attribute names during extraction. + attrNames: re.SubexpNames(), + } + + sp.toAttributeRules = append(sp.toAttributeRules, rule) + } + } + + return sp, nil +} + +func (sp *spanProcessor) ProcessTraces(_ context.Context, td pdata.Traces) (pdata.Traces, error) { + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + ilss := rs.InstrumentationLibrarySpans() + resource := rs.Resource() + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + spans := ils.Spans() + library := ils.InstrumentationLibrary() + for k := 0; k < spans.Len(); k++ { + s := spans.At(k) + if filterspan.SkipSpan(sp.include, sp.exclude, s, resource, library) { + continue + } + sp.processFromAttributes(s) + sp.processToAttributes(s) + } + } + } + return td, nil +} + +func (sp *spanProcessor) processFromAttributes(span pdata.Span) { + if len(sp.config.Rename.FromAttributes) == 0 { + // There is FromAttributes rule. + return + } + + attrs := span.Attributes() + if attrs.Len() == 0 { + // There are no attributes to create span name from. + return + } + + // Note: There was a separate proposal for creating the string. + // With benchmarking, strings.Builder is faster than the proposal. + // For full context, refer to this PR comment: + // https://go.opentelemetry.io/collector/pull/301#discussion_r318357678 + var sb strings.Builder + for i, key := range sp.config.Rename.FromAttributes { + attr, found := attrs.Get(key) + + // If one of the keys isn't found, the span name is not updated. + if !found { + return + } + + // Note: WriteString() always return a nil error so there is no error checking + // for this method call. + // https://golang.org/src/strings/builder.go?s=3425:3477#L110 + + // Include the separator before appending an attribute value if: + // this isn't the first value(ie i == 0) loop through the FromAttributes + // and + // the separator isn't an empty string. + if i > 0 && sp.config.Rename.Separator != "" { + sb.WriteString(sp.config.Rename.Separator) + } + + switch attr.Type() { + case pdata.AttributeValueSTRING: + sb.WriteString(attr.StringVal()) + case pdata.AttributeValueBOOL: + sb.WriteString(strconv.FormatBool(attr.BoolVal())) + case pdata.AttributeValueDOUBLE: + sb.WriteString(strconv.FormatFloat(attr.DoubleVal(), 'f', -1, 64)) + case pdata.AttributeValueINT: + sb.WriteString(strconv.FormatInt(attr.IntVal(), 10)) + default: + sb.WriteString("") + } + } + span.SetName(sb.String()) +} + +func (sp *spanProcessor) processToAttributes(span pdata.Span) { + if span.Name() == "" { + // There is no span name to work on. + return + } + + if sp.config.Rename.ToAttributes == nil { + // No rules to apply. + return + } + + // Process rules one by one. Store results of processing in the span + // so that each subsequent rule works on the span name that is the output + // after processing the previous rule. + for _, rule := range sp.toAttributeRules { + re := rule.re + oldName := span.Name() + + // Match the regular expression and extract matched subexpressions. + submatches := re.FindStringSubmatch(oldName) + if submatches == nil { + continue + } + // There is a match. We will also need positions of subexpression matches. + submatchIdxPairs := re.FindStringSubmatchIndex(oldName) + + // A place to accumulate new span name. + var sb strings.Builder + + // Index in the oldName until which we traversed. + var oldNameIndex = 0 + + attrs := span.Attributes() + + // TODO: Pre-allocate len(submatches) space in the attributes. + + // Start from index 1, which is the first submatch (index 0 is the entire match). + // We will go over submatches and will simultaneously build a new span name, + // replacing matched subexpressions by attribute names. + for i := 1; i < len(submatches); i++ { + attrs.UpsertString(rule.attrNames[i], submatches[i]) + + // Add part of span name from end of previous match to start of this match + // and then add attribute name wrapped in curly brackets. + matchStartIndex := submatchIdxPairs[i*2] // start of i'th submatch. + sb.WriteString(oldName[oldNameIndex:matchStartIndex] + "{" + rule.attrNames[i] + "}") + + // Advance the index to the end of current match. + oldNameIndex = submatchIdxPairs[i*2+1] // end of i'th submatch. + } + if oldNameIndex < len(oldName) { + // Append the remainder, from the end of last match until end of span name. + sb.WriteString(oldName[oldNameIndex:]) + } + + // Set new span name. + span.SetName(sb.String()) + + if sp.config.Rename.ToAttributes.BreakAfterMatch { + // Stop processing, break after first match is requested. + break + } + } +} diff --git a/internal/otel_collector/processor/spanprocessor/span_test.go b/internal/otel_collector/processor/spanprocessor/span_test.go new file mode 100644 index 00000000000..df4fe1b5b27 --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/span_test.go @@ -0,0 +1,599 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterconfig" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func TestNewTraceProcessor(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"foo"} + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, nil) + require.Error(t, componenterror.ErrNilNextConsumer, err) + require.Nil(t, tp) + + tp, err = factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{}, cfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) +} + +// Common structure for the test cases. +type testCase struct { + serviceName string + inputName string + inputAttributes map[string]pdata.AttributeValue + outputName string + outputAttributes map[string]pdata.AttributeValue +} + +// runIndividualTestCase is the common logic of passing trace data through a configured attributes processor. +func runIndividualTestCase(t *testing.T, tt testCase, tp component.TracesProcessor) { + t.Run(tt.inputName, func(t *testing.T) { + td := generateTraceData(tt.serviceName, tt.inputName, tt.inputAttributes) + + assert.NoError(t, tp.ConsumeTraces(context.Background(), td)) + // Ensure that the modified `td` has the attributes sorted: + rss := td.ResourceSpans() + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + rs.Resource().Attributes().Sort() + ilss := rs.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + spans := ilss.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + spans.At(k).Attributes().Sort() + } + } + } + assert.EqualValues(t, generateTraceData(tt.serviceName, tt.outputName, tt.outputAttributes), td) + }) +} + +func generateTraceData(serviceName, inputName string, attrs map[string]pdata.AttributeValue) pdata.Traces { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + rs := td.ResourceSpans().At(0) + if serviceName != "" { + rs.Resource().Attributes().UpsertString(conventions.AttributeServiceName, serviceName) + } + rs.InstrumentationLibrarySpans().Resize(1) + ils := rs.InstrumentationLibrarySpans().At(0) + spans := ils.Spans() + spans.Resize(1) + spans.At(0).SetName(inputName) + spans.At(0).Attributes().InitFromMap(attrs).Sort() + return td +} + +// TestSpanProcessor_Values tests all possible value types. +func TestSpanProcessor_NilEmptyData(t *testing.T) { + type nilEmptyTestCase struct { + name string + input pdata.Traces + output pdata.Traces + } + // TODO: Add test for "nil" Span. This needs support from data slices to allow to construct that. + testCases := []nilEmptyTestCase{ + { + name: "empty", + input: testdata.GenerateTraceDataEmpty(), + output: testdata.GenerateTraceDataEmpty(), + }, + { + name: "one-empty-resource-spans", + input: testdata.GenerateTraceDataOneEmptyResourceSpans(), + output: testdata.GenerateTraceDataOneEmptyResourceSpans(), + }, + { + name: "no-libraries", + input: testdata.GenerateTraceDataNoLibraries(), + output: testdata.GenerateTraceDataNoLibraries(), + }, + { + name: "one-empty-instrumentation-library", + input: testdata.GenerateTraceDataOneEmptyInstrumentationLibrary(), + output: testdata.GenerateTraceDataOneEmptyInstrumentationLibrary(), + }, + } + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Include = &filterconfig.MatchProperties{ + Config: *createMatchConfig(filterset.Strict), + Services: []string{"service"}, + } + oCfg.Rename.FromAttributes = []string{"key"} + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + for i := range testCases { + tt := testCases[i] + t.Run(tt.name, func(t *testing.T) { + assert.NoError(t, tp.ConsumeTraces(context.Background(), tt.input)) + assert.EqualValues(t, tt.output, tt.input) + }) + } +} + +// TestSpanProcessor_Values tests all possible value types. +func TestSpanProcessor_Values(t *testing.T) { + // TODO: Add test for "nil" Span. This needs support from data slices to allow to construct that. + testCases := []testCase{ + { + inputName: "", + inputAttributes: nil, + outputName: "", + outputAttributes: nil, + }, + { + inputName: "nil-attributes", + inputAttributes: nil, + outputName: "nil-attributes", + outputAttributes: nil, + }, + { + inputName: "empty-attributes", + inputAttributes: map[string]pdata.AttributeValue{}, + outputName: "empty-attributes", + outputAttributes: map[string]pdata.AttributeValue{}, + }, + { + inputName: "string-type", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + }, + outputName: "bob", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + }, + }, + { + inputName: "int-type", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueInt(123), + }, + outputName: "123", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueInt(123), + }, + }, + { + inputName: "double-type", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueDouble(234.129312), + }, + outputName: "234.129312", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueDouble(234.129312), + }, + }, + { + inputName: "bool-type", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueBool(true), + }, + outputName: "true", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueBool(true), + }, + }, + // TODO: What do we do when AttributeMap contains a nil entry? Is that possible? + // TODO: In the new protocol do we want to support unknown type as 0 instead of string? + // TODO: Do we want to allow constructing entries with unknown type? + /*{ + inputName: "nil-type", + inputAttributes: map[string]data.AttributeValue{ + "key1": data.NewAttributeValue(), + }, + outputName: "", + outputAttributes: map[string]data.AttributeValue{ + "key1": data.NewAttributeValue(), + }, + }, + { + inputName: "unknown-type", + inputAttributes: map[string]data.AttributeValue{ + "key1": {}, + }, + outputName: "", + outputAttributes: map[string]data.AttributeValue{ + "key1": {}, + }, + },*/ + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"key1"} + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + for _, tc := range testCases { + runIndividualTestCase(t, tc, tp) + } +} + +// TestSpanProcessor_MissingKeys tests that missing a key in an attribute map results in no span name changes. +func TestSpanProcessor_MissingKeys(t *testing.T) { + testCases := []testCase{ + { + inputName: "first-keys-missing", + inputAttributes: map[string]pdata.AttributeValue{ + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + "key4": pdata.NewAttributeValueBool(true), + }, + outputName: "first-keys-missing", + outputAttributes: map[string]pdata.AttributeValue{ + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + "key4": pdata.NewAttributeValueBool(true), + }, + }, + { + inputName: "middle-key-missing", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key4": pdata.NewAttributeValueBool(true), + }, + outputName: "middle-key-missing", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key4": pdata.NewAttributeValueBool(true), + }, + }, + { + inputName: "last-key-missing", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + }, + outputName: "last-key-missing", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + }, + }, + { + inputName: "all-keys-exists", + inputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + "key4": pdata.NewAttributeValueBool(true), + }, + outputName: "bob::123::234.129312::true", + outputAttributes: map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + "key4": pdata.NewAttributeValueBool(true), + }, + }, + } + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"key1", "key2", "key3", "key4"} + oCfg.Rename.Separator = "::" + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + for _, tc := range testCases { + runIndividualTestCase(t, tc, tp) + } +} + +// TestSpanProcessor_Separator ensures naming a span with a single key and separator will only contain the value from +// the single key. +func TestSpanProcessor_Separator(t *testing.T) { + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"key1"} + oCfg.Rename.Separator = "::" + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + traceData := generateTraceData( + "", + "ensure no separator in the rename with one key", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + }) + assert.NoError(t, tp.ConsumeTraces(context.Background(), traceData)) + + assert.Equal(t, generateTraceData( + "", + "bob", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + }), traceData) +} + +// TestSpanProcessor_NoSeparatorMultipleKeys tests naming a span using multiple keys and no separator. +func TestSpanProcessor_NoSeparatorMultipleKeys(t *testing.T) { + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"key1", "key2"} + oCfg.Rename.Separator = "" + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + traceData := generateTraceData( + "", + "ensure no separator in the rename with two keys", map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + }) + assert.NoError(t, tp.ConsumeTraces(context.Background(), traceData)) + + assert.Equal(t, generateTraceData( + "", + "bob123", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + }), traceData) +} + +// TestSpanProcessor_SeparatorMultipleKeys tests naming a span with multiple keys and a separator. +func TestSpanProcessor_SeparatorMultipleKeys(t *testing.T) { + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"key1", "key2", "key3", "key4"} + oCfg.Rename.Separator = "::" + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + traceData := generateTraceData( + "", + "rename with separators and multiple keys", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + "key4": pdata.NewAttributeValueBool(true), + }) + assert.NoError(t, tp.ConsumeTraces(context.Background(), traceData)) + + assert.Equal(t, generateTraceData( + "", + "bob::123::234.129312::true", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + "key2": pdata.NewAttributeValueInt(123), + "key3": pdata.NewAttributeValueDouble(234.129312), + "key4": pdata.NewAttributeValueBool(true), + }), traceData) +} + +// TestSpanProcessor_NilName tests naming a span when the input span had no name. +func TestSpanProcessor_NilName(t *testing.T) { + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.FromAttributes = []string{"key1"} + oCfg.Rename.Separator = "::" + + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + traceData := generateTraceData( + "", + "", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + }) + assert.NoError(t, tp.ConsumeTraces(context.Background(), traceData)) + + assert.Equal(t, generateTraceData( + "", + "bob", + map[string]pdata.AttributeValue{ + "key1": pdata.NewAttributeValueString("bob"), + }), traceData) +} + +// TestSpanProcessor_ToAttributes +func TestSpanProcessor_ToAttributes(t *testing.T) { + + testCases := []struct { + rules []string + breakAfterMatch bool + testCase + }{ + { + rules: []string{`^\/api\/v1\/document\/(?P.*)\/update\/1$`}, + testCase: testCase{ + inputName: "/api/v1/document/321083210/update/1", + inputAttributes: map[string]pdata.AttributeValue{}, + outputName: "/api/v1/document/{documentId}/update/1", + outputAttributes: map[string]pdata.AttributeValue{ + "documentId": pdata.NewAttributeValueString("321083210"), + }, + }, + }, + + { + rules: []string{`^\/api\/(?P.*)\/document\/(?P.*)\/update\/2$`}, + testCase: testCase{ + inputName: "/api/v1/document/321083210/update/2", + outputName: "/api/{version}/document/{documentId}/update/2", + outputAttributes: map[string]pdata.AttributeValue{ + "documentId": pdata.NewAttributeValueString("321083210"), + "version": pdata.NewAttributeValueString("v1"), + }, + }, + }, + + { + rules: []string{`^\/api\/.*\/document\/(?P.*)\/update\/3$`, + `^\/api\/(?P.*)\/document\/.*\/update\/3$`}, + testCase: testCase{ + inputName: "/api/v1/document/321083210/update/3", + outputName: "/api/{version}/document/{documentId}/update/3", + outputAttributes: map[string]pdata.AttributeValue{ + "documentId": pdata.NewAttributeValueString("321083210"), + "version": pdata.NewAttributeValueString("v1"), + }, + }, + breakAfterMatch: false, + }, + + { + rules: []string{`^\/api\/v1\/document\/(?P.*)\/update\/4$`, + `^\/api\/(?P.*)\/document\/(?P.*)\/update\/4$`}, + testCase: testCase{ + inputName: "/api/v1/document/321083210/update/4", + outputName: "/api/v1/document/{documentId}/update/4", + outputAttributes: map[string]pdata.AttributeValue{ + "documentId": pdata.NewAttributeValueString("321083210"), + }, + }, + breakAfterMatch: true, + }, + + { + rules: []string{"rule"}, + testCase: testCase{ + inputName: "", + outputName: "", + outputAttributes: nil, + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Rename.ToAttributes = &ToAttributes{} + + for _, tc := range testCases { + oCfg.Rename.ToAttributes.Rules = tc.rules + oCfg.Rename.ToAttributes.BreakAfterMatch = tc.breakAfterMatch + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + runIndividualTestCase(t, tc.testCase, tp) + } +} + +func TestSpanProcessor_skipSpan(t *testing.T) { + testCases := []testCase{ + { + serviceName: "bankss", + inputName: "url/url", + outputName: "url/url", + }, + { + serviceName: "banks", + inputName: "noslasheshere", + outputName: "noslasheshere", + }, + { + serviceName: "banks", + inputName: "www.test.com/code", + outputName: "{operation_website}", + outputAttributes: map[string]pdata.AttributeValue{ + "operation_website": pdata.NewAttributeValueString("www.test.com/code"), + }, + }, + { + serviceName: "banks", + inputName: "donot/", + inputAttributes: map[string]pdata.AttributeValue{ + "operation_website": pdata.NewAttributeValueString("www.test.com/code"), + }, + outputName: "{operation_website}", + outputAttributes: map[string]pdata.AttributeValue{ + "operation_website": pdata.NewAttributeValueString("donot/"), + }, + }, + { + serviceName: "banks", + inputName: "donot/change", + inputAttributes: map[string]pdata.AttributeValue{ + "operation_website": pdata.NewAttributeValueString("www.test.com/code"), + }, + outputName: "donot/change", + outputAttributes: map[string]pdata.AttributeValue{ + "operation_website": pdata.NewAttributeValueString("www.test.com/code"), + }, + }, + } + + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.Include = &filterconfig.MatchProperties{ + Config: *createMatchConfig(filterset.Regexp), + Services: []string{`^banks$`}, + SpanNames: []string{"/"}, + } + oCfg.Exclude = &filterconfig.MatchProperties{ + Config: *createMatchConfig(filterset.Strict), + SpanNames: []string{`donot/change`}, + } + oCfg.Rename.ToAttributes = &ToAttributes{ + Rules: []string{`(?P.*?)$`}, + } + tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateParams{Logger: zap.NewNop()}, oCfg, consumertest.NewTracesNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + for _, tc := range testCases { + runIndividualTestCase(t, tc, tp) + } +} diff --git a/internal/otel_collector/processor/spanprocessor/testdata/config.yaml b/internal/otel_collector/processor/spanprocessor/testdata/config.yaml new file mode 100644 index 00000000000..67d75e61c5f --- /dev/null +++ b/internal/otel_collector/processor/spanprocessor/testdata/config.yaml @@ -0,0 +1,95 @@ +receivers: + examplereceiver: + +processors: + # The following specifies the values of attribute `db.svc`, `operation`, + # and `id` will form the new name of the span, in that order, separated by the + # value `::`. All attribute keys needs to be specified in the span for + # the processor to rename it. + # Note: There is no default configuration for the span processor. For 'name', + # the field `from_attributes` is required. + # + # Example 1 - All keys are in the span: + # Span name before processor: + # "Span.Name": "serviceA" + # Attributes Key/Value pair for a span + # { "db.svc": "location", "operation": "get", "id": "1234"} + # Separator: "::" + # Results in the following new span name: + # "Span.Name": "location::get::1234" + # + # Example 2 - Some keys are missing from the span. + # Span name(before processor): + # "Span.Name": "serviceA" + # Attributes Key/Value pair for a span + # { "db.svc": "location", "id": "1234"} + # Separator: "::" + # Results in no new name because the attribute key `operation` isn't set. + # Span name after processor: + # "Span.Name": "serviceA" + span/custom: + name: + separator: "::" + from_attributes: [db.svc, operation, id] + + # The following specifies generating a span name with no separator. + # Example: + # Attributes Key/Value pair + # { "db.svc": "location:, "operation": "get", "id": "1234"} + # Separator: "" + # Results in the following new span name: + # "locationget1234" + span/no-separator: + name: + from_attributes: [db.svc, operation, id] + + # The following extracts attributes from span name and replaces extracted + # parts with attribute names. + # to_attributes is a list of rules that extract attribute values from span name and + # replace them by attributes names in the span name. Each rule in the list is + # regex pattern string. Span name is checked against the regex and if the regex matches + # all named subexpressions from the regex then the matches are extracted as attributes + # and added to the span. Subexpression name becomes the attribute name and + # subexpression matched portion becomes the attribute value. The matched portion + # in the span name is replaced by extracted attribute name. If the attributes exist + # they will be overwritten. Checks are performed for elements in this array in the + # order they are specified. + # + # Example: + # Let's assume input span name is /api/v1/document/12345678/update + # Applying the following results in output span name /api/v1/document/{documentId}/update + # and will add a new attribute "documentId"="12345678" to the span. + span/to_attributes: + name: + to_attributes: + rules: + - ^\/api\/v1\/document\/(?P.*)\/update$ + + # The following demonstrates renaming the span name to `{operation_website}` + # and adding the attribute {Key: operation_website, Value: } + # when the span has the following properties + # - Services names that contain the word `banks`. + # - The span name contains '/' anywhere in the string. + # - The span name is not 'donot/change'. + span/includeexclude: + include: + match_type: regexp + services: ["banks"] + span_names: ["^(.*?)/(.*?)$"] + exclude: + match_type: strict + span_names: ["donot/change"] + name: + to_attributes: + rules: + - "(?P.*?)$" + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [span/custom] + exporters: [exampleexporter] diff --git a/internal/otel_collector/proto_patch.sed b/internal/otel_collector/proto_patch.sed new file mode 100644 index 00000000000..c83bfd00ba2 --- /dev/null +++ b/internal/otel_collector/proto_patch.sed @@ -0,0 +1,40 @@ +s+github.com/open-telemetry/opentelemetry-proto/gen/go/+go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/+g + +s+package opentelemetry.proto.\(.*\).v1;+package opentelemetry.proto.\1.v1;\ +\ +import "gogoproto/gogo.proto";+g + +s+bytes trace_id = \(.*\);+bytes trace_id = \1\ + [\ + // Use custom TraceId data type for this field.\ + (gogoproto.nullable) = false,\ + (gogoproto.customtype) = "go.opentelemetry.io/collector/internal/data.TraceID"\ + ];+g + +s+bytes \(.*span_id\) = \(.*\);+bytes \1 = \2\ + [\ + // Use custom SpanId data type for this field.\ + (gogoproto.nullable) = false,\ + (gogoproto.customtype) = "go.opentelemetry.io/collector/internal/data.SpanID"\ + ];+g + +s+repeated opentelemetry.proto.common.v1.KeyValue \(.*\);+repeated opentelemetry.proto.common.v1.KeyValue \1\ + [ (gogoproto.nullable) = false ];+g + +s+repeated KeyValue \(.*\);+repeated KeyValue \1\ + [ (gogoproto.nullable) = false ];+g + +s+AnyValue \(.*\);+AnyValue \1\ + [ (gogoproto.nullable) = false ];+g + +s+repeated opentelemetry.proto.common.v1.StringKeyValue \(.*\);+repeated opentelemetry.proto.common.v1.StringKeyValue \1\ + [ (gogoproto.nullable) = false ];+g + +s+opentelemetry.proto.resource.v1.Resource resource = \(.*\);+opentelemetry.proto.resource.v1.Resource resource = \1\ + [ (gogoproto.nullable) = false ];+g + +s+opentelemetry.proto.common.v1.InstrumentationLibrary instrumentation_library = \(.*\);+opentelemetry.proto.common.v1.InstrumentationLibrary instrumentation_library = \1\ + [ (gogoproto.nullable) = false ];+g + +s+Status \(.*\);+Status \1\ + [ (gogoproto.nullable) = false ];+g diff --git a/internal/otel_collector/receiver/README.md b/internal/otel_collector/receiver/README.md new file mode 100644 index 00000000000..b3fcf3eb010 --- /dev/null +++ b/internal/otel_collector/receiver/README.md @@ -0,0 +1,83 @@ +# General Information + +A receiver is how data gets into the OpenTelemetry Collector. Generally, a +receiver accepts data in a specified format, translates it into the internal +format and passes it to [processors](../processor/README.md) and +[exporters](../exporter/README.md) defined in the applicable +pipelines. + +Available trace receivers (sorted alphabetically): + +- [Jaeger Receiver](jaegerreceiver/README.md) +- [Kafka Receiver](kafkareceiver/README.md) +- [OpenCensus Receiver](opencensusreceiver/README.md) +- [OTLP Receiver](otlpreceiver/README.md) +- [Zipkin Receiver](zipkinreceiver/README.md) + +Available metric receivers (sorted alphabetically): + +- [Host Metrics Receiver](hostmetricsreceiver/README.md) +- [OpenCensus Receiver](opencensusreceiver/README.md) +- [OTLP Receiver](otlpreceiver/README.md) +- [Prometheus Receiver](prometheusreceiver/README.md) + +Available log receivers (sorted alphabetically): + +- [Fluent Forward Receiver](fluentforwardreceiver/README.md) +- [OTLP Receiver](otlpreceiver/README.md) + +The [contrib repository](https://github.com/open-telemetry/opentelemetry-collector-contrib) + has more receivers that can be added to custom builds of the collector. + +## Configuring Receivers + +Receivers are configured via YAML under the top-level `receivers` tag. There +must be at least one enabled receiver for a configuration to be considered +valid. + +The following is a sample configuration for the `examplereceiver`. + +```yaml +receivers: + # Receiver 1. + # : + examplereceiver: + # : + endpoint: 1.2.3.4:8080 + # ... + # Receiver 2. + # /: + examplereceiver/settings: + # : + endpoint: 0.0.0.0:9211 +``` + +A receiver instance is referenced by its full name in other parts of the config, +such as in pipelines. A full name consists of the receiver type, '/' and the +name appended to the receiver type in the configuration. All receiver full names +must be unique. + +For the example above: + +- Receiver 1 has full name `examplereceiver`. +- Receiver 2 has full name `examplereceiver/settings`. + +Receivers are enabled upon being added to a pipeline. For example: + +```yaml +service: + pipelines: + # Valid pipelines are: traces, metrics or logs + # Trace pipeline 1. + traces: + receivers: [examplereceiver, examplereceiver/settings] + processors: [] + exporters: [exampleexporter] + # Trace pipeline 2. + traces/another: + receivers: [examplereceiver, examplereceiver/settings] + processors: [] + exporters: [exampleexporter] +``` + +> At least one receiver must be enabled per pipeline to be a valid configuration. \ No newline at end of file diff --git a/internal/otel_collector/receiver/doc.go b/internal/otel_collector/receiver/doc.go new file mode 100644 index 00000000000..f9d6d670038 --- /dev/null +++ b/internal/otel_collector/receiver/doc.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package receiver contains implementations of Receiver components. +// +// To implement a custom receiver you will need to implement component.ReceiverFactory +// interface and component.Receiver interface. +// +// To make the custom receiver part of the Collector build the factory must be added +// to defaultcomponents.Components() function. +package receiver diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/README.md b/internal/otel_collector/receiver/fluentforwardreceiver/README.md new file mode 100644 index 00000000000..51549cb34dd --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/README.md @@ -0,0 +1,33 @@ +# Fluent Forward Receiver + +This receiver runs a TCP server that accepts events via the [Fluent Forward +protocol](https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1). + +This receiver: + + - Does **not** support TLS or the handshake portion of the Forward protocol. + - Does support acknowledgments of events that have the `chunk` option, as per the spec. + - Supports all three event types (message, forward, packed forward, including + compressed packed forward) + - Supports listening on a Unix domain socket by making the `listenAddress` + option of the form `unix://`. + - If using TCP, it will start a UDP server on the same port to deliver + heartbeat echos, as per the spec. + +Here is a basic example config that makes the receiver listen on all interfaces +on port 8006: + +```yaml +receivers: + fluentforward: + endpoint: 0.0.0.0:8006 +``` + + +## Development + +If you are working on this receiver and need to regenerate any of the message +pack autogenerated code, just run `go generate` on this package and its +subpackages. You can get the `msgp` binary by just running `go get -u -t +github.com/tinylib/msgp`, and make sure the Go binary path is on your shell's +PATH. diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/ack.go b/internal/otel_collector/receiver/fluentforwardreceiver/ack.go new file mode 100644 index 00000000000..dba73f616b7 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/ack.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import "github.com/tinylib/msgp/msgp" + +type AckResponse struct { + Ack string `msg:"ack"` +} + +func (z AckResponse) EncodeMsg(en *msgp.Writer) error { + // map header, size 1 + // write "ack" + err := en.Append(0x81, 0xa3, 0x61, 0x63, 0x6b) + if err != nil { + return err + } + + err = en.WriteString(z.Ack) + if err != nil { + return msgp.WrapError(err, "Ack") + } + return nil +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/ack_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/ack_test.go new file mode 100644 index 00000000000..f08146872e8 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/ack_test.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tinylib/msgp/msgp" + + "go.opentelemetry.io/collector/testutil" +) + +func msgpWriterWithLimit(t *testing.T, l int) *msgp.Writer { + // NewWriterSize forces size to be at least 18 bytes so just use that as + // the floor and write nulls to those first 18 bytes to make the limit + // truly l. + w := msgp.NewWriterSize(&testutil.LimitedWriter{ + MaxLen: l, + }, 18+l) + _, err := w.Write(bytes.Repeat([]byte{0x00}, 18)) + require.NoError(t, err) + return w +} + +func TestAckEncoding(t *testing.T) { + a := &AckResponse{ + Ack: "test", + } + + err := a.EncodeMsg(msgpWriterWithLimit(t, 1000)) + require.Nil(t, err) + + err = a.EncodeMsg(msgpWriterWithLimit(t, 4)) + require.NotNil(t, err) + + err = a.EncodeMsg(msgpWriterWithLimit(t, 7)) + require.NotNil(t, err) +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/collector.go b/internal/otel_collector/receiver/fluentforwardreceiver/collector.go new file mode 100644 index 00000000000..1a54c2d45c2 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/collector.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + + "go.opencensus.io/stats" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/fluentforwardreceiver/observ" +) + +// Collector acts as an aggregator of LogRecords so that we don't have to +// generate as many pdata.Logs instances...we can pre-batch the LogRecord +// instances from several Forward events into one to hopefully reduce +// allocations and GC overhead. +type Collector struct { + nextConsumer consumer.LogsConsumer + eventCh <-chan Event + logger *zap.Logger +} + +func newCollector(eventCh <-chan Event, next consumer.LogsConsumer, logger *zap.Logger) *Collector { + return &Collector{ + nextConsumer: next, + eventCh: eventCh, + logger: logger, + } +} + +func (c *Collector) Start(ctx context.Context) { + go c.processEvents(ctx) +} + +func (c *Collector) processEvents(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case e := <-c.eventCh: + buffered := []Event{e} + // Pull out anything waiting on the eventCh to get better + // efficiency on LogResource allocations. + buffered = fillBufferUntilChanEmpty(c.eventCh, buffered) + + logs := collectLogRecords(buffered) + c.nextConsumer.ConsumeLogs(ctx, logs) + } + } +} + +func fillBufferUntilChanEmpty(eventCh <-chan Event, buf []Event) []Event { + for { + select { + case e2 := <-eventCh: + buf = append(buf, e2) + default: + return buf + } + } +} + +func collectLogRecords(events []Event) pdata.Logs { + out := pdata.NewLogs() + + logs := out.ResourceLogs() + + logs.Resize(1) + rls := logs.At(0) + + rls.InstrumentationLibraryLogs().Resize(1) + logSlice := rls.InstrumentationLibraryLogs().At(0).Logs() + + for i := range events { + events[i].LogRecords().MoveAndAppendTo(logSlice) + } + + stats.Record(context.Background(), observ.RecordsGenerated.M(int64(out.LogRecordCount()))) + + return out +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/config.go b/internal/otel_collector/receiver/fluentforwardreceiver/config.go new file mode 100644 index 00000000000..37af82ae14f --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/config.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for the SignalFx receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // The address to listen on for incoming Fluent Forward events. Should be + // of the form `:` (TCP) or `unix://` (Unix + // domain socket). + ListenAddress string `mapstructure:"endpoint"` +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/config_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/config_test.go new file mode 100644 index 00000000000..eb90f7ad7ca --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/config_test.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.Nil(t, err) + + factory := NewFactory() + factories.Receivers[configmodels.Type(typeStr)] = factory + cfg, err := configtest.LoadConfigFile( + t, path.Join(".", "testdata", "config.yaml"), factories, + ) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 1) + + r0 := cfg.Receivers["fluentforward"] + assert.Equal(t, r0, factory.CreateDefaultConfig()) + +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/conversion.go b/internal/otel_collector/receiver/fluentforwardreceiver/conversion.go new file mode 100644 index 00000000000..74e3b0febe2 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/conversion.go @@ -0,0 +1,412 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "time" + + "github.com/tinylib/msgp/msgp" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const tagAttributeKey = "fluent.tag" + +// Most of this logic is derived directly from +// https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1, +// which describes the fields in much greater detail. + +type Event interface { + DecodeMsg(dc *msgp.Reader) error + LogRecords() pdata.LogSlice + Chunk() string + Compressed() string +} + +type OptionsMap map[string]interface{} + +// Chunk returns the `chunk` option or blank string if it was not set. +func (om OptionsMap) Chunk() string { + c, _ := om["chunk"].(string) + return c +} + +func (om OptionsMap) Compressed() string { + compressed, _ := om["compressed"].(string) + return compressed +} + +type EventMode int + +type Peeker interface { + Peek(n int) ([]byte, error) +} + +const ( + UnknownMode EventMode = iota + MessageMode + ForwardMode + PackedForwardMode +) + +func (em EventMode) String() string { + switch em { + case UnknownMode: + return "unknown" + case MessageMode: + return "message" + case ForwardMode: + return "forward" + case PackedForwardMode: + return "packedforward" + default: + panic("programmer bug") + } +} + +func insertToAttributeMap(key string, val interface{}, dest *pdata.AttributeMap) { + // See https://github.com/tinylib/msgp/wiki/Type-Mapping-Rules + switch r := val.(type) { + case bool: + dest.InsertBool(key, r) + case string: + dest.InsertString(key, r) + case uint64: + dest.InsertInt(key, int64(r)) + case int64: + dest.InsertInt(key, r) + case []byte: + dest.InsertString(key, string(r)) + case map[string]interface{}, []interface{}: + encoded, err := json.Marshal(r) + if err != nil { + dest.InsertString(key, err.Error()) + } + dest.InsertString(key, string(encoded)) + case float32: + dest.InsertDouble(key, float64(r)) + case float64: + dest.InsertDouble(key, r) + default: + dest.InsertString(key, fmt.Sprintf("%v", r)) + } +} + +func timeFromTimestamp(ts interface{}) (time.Time, error) { + switch v := ts.(type) { + case int64: + return time.Unix(v, 0), nil + case *EventTimeExt: + return time.Time(*v), nil + default: + return time.Time{}, fmt.Errorf("unknown type of value: %v", ts) + } +} + +func decodeTimestampToLogRecord(dc *msgp.Reader, lr pdata.LogRecord) error { + tsIntf, err := dc.ReadIntf() + if err != nil { + return msgp.WrapError(err, "Time") + } + + ts, err := timeFromTimestamp(tsIntf) + if err != nil { + return msgp.WrapError(err, "Time") + } + + lr.SetTimestamp(pdata.TimestampUnixNano(ts.UnixNano())) + return nil +} + +func parseRecordToLogRecord(dc *msgp.Reader, lr pdata.LogRecord) error { + attrs := lr.Attributes() + + recordLen, err := dc.ReadMapHeader() + if err != nil { + return msgp.WrapError(err, "Record") + } + + for recordLen > 0 { + recordLen-- + key, err := dc.ReadString() + if err != nil { + return msgp.WrapError(err, "Record") + } + val, err := dc.ReadIntf() + if err != nil { + return msgp.WrapError(err, "Record", key) + } + + if s, ok := val.(string); ok && key == "log" { + lr.Body().SetStringVal(s) + } else { + insertToAttributeMap(key, val, &attrs) + } + } + + return nil +} + +type MessageEventLogRecord struct { + pdata.LogSlice + OptionsMap +} + +func (melr *MessageEventLogRecord) LogRecords() pdata.LogSlice { + return melr.LogSlice +} + +func (melr *MessageEventLogRecord) DecodeMsg(dc *msgp.Reader) error { + melr.LogSlice = pdata.NewLogSlice() + melr.LogSlice.Resize(1) + + var arrLen uint32 + var err error + + arrLen, err = dc.ReadArrayHeader() + if err != nil { + return msgp.WrapError(err) + } + if arrLen > 4 || arrLen < 3 { + return msgp.ArrayError{Wanted: 3, Got: arrLen} + } + + tag, err := dc.ReadString() + if err != nil { + return msgp.WrapError(err, "Tag") + } + + attrs := melr.LogSlice.At(0).Attributes() + attrs.InsertString(tagAttributeKey, tag) + + err = decodeTimestampToLogRecord(dc, melr.LogSlice.At(0)) + if err != nil { + return msgp.WrapError(err, "Time") + } + + err = parseRecordToLogRecord(dc, melr.LogSlice.At(0)) + if err != nil { + return err + } + + if arrLen == 4 { + melr.OptionsMap, err = parseOptions(dc) + if err != nil { + return err + } + } + return nil +} + +func parseOptions(dc *msgp.Reader) (OptionsMap, error) { + var optionLen uint32 + optionLen, err := dc.ReadMapHeader() + if err != nil { + return nil, msgp.WrapError(err, "Option") + } + out := make(OptionsMap, optionLen) + + for optionLen > 0 { + optionLen-- + key, err := dc.ReadString() + if err != nil { + return nil, msgp.WrapError(err, "Option") + } + val, err := dc.ReadIntf() + if err != nil { + return nil, msgp.WrapError(err, "Option", key) + } + out[key] = val + } + return out, nil +} + +type ForwardEventLogRecords struct { + pdata.LogSlice + OptionsMap +} + +func (fe *ForwardEventLogRecords) LogRecords() pdata.LogSlice { + return fe.LogSlice +} + +func (fe *ForwardEventLogRecords) DecodeMsg(dc *msgp.Reader) (err error) { + fe.LogSlice = pdata.NewLogSlice() + + var arrLen uint32 + arrLen, err = dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + if arrLen < 2 || arrLen > 3 { + err = msgp.ArrayError{Wanted: 2, Got: arrLen} + return + } + + tag, err := dc.ReadString() + if err != nil { + return msgp.WrapError(err, "Tag") + } + + entryLen, err := dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err, "Record") + return + } + + fe.LogSlice.Resize(int(entryLen)) + for i := 0; i < int(entryLen); i++ { + lr := fe.LogSlice.At(i) + + err = parseEntryToLogRecord(dc, lr) + if err != nil { + return msgp.WrapError(err, "Entries", i) + } + fe.LogSlice.At(i).Attributes().InsertString(tagAttributeKey, tag) + } + + if arrLen == 3 { + fe.OptionsMap, err = parseOptions(dc) + if err != nil { + return err + } + } + + return +} + +func parseEntryToLogRecord(dc *msgp.Reader, lr pdata.LogRecord) error { + arrLen, err := dc.ReadArrayHeader() + if err != nil { + return msgp.WrapError(err) + } + if arrLen != 2 { + return msgp.ArrayError{Wanted: 2, Got: arrLen} + } + + err = decodeTimestampToLogRecord(dc, lr) + if err != nil { + return msgp.WrapError(err, "Time") + } + + return parseRecordToLogRecord(dc, lr) +} + +type PackedForwardEventLogRecords struct { + pdata.LogSlice + OptionsMap +} + +func (pfe *PackedForwardEventLogRecords) LogRecords() pdata.LogSlice { + return pfe.LogSlice +} + +// DecodeMsg implements msgp.Decodable. This was originally code generated but +// then manually copied here in order to handle the optional Options field. +func (pfe *PackedForwardEventLogRecords) DecodeMsg(dc *msgp.Reader) error { + pfe.LogSlice = pdata.NewLogSlice() + + arrLen, err := dc.ReadArrayHeader() + if err != nil { + return msgp.WrapError(err) + } + if arrLen < 2 || arrLen > 3 { + return msgp.ArrayError{Wanted: 2, Got: arrLen} + } + + tag, err := dc.ReadString() + if err != nil { + return msgp.WrapError(err, "Tag") + } + + entriesFirstByte, err := dc.R.Peek(1) + if err != nil { + return msgp.WrapError(err, "EntriesRaw") + } + + entriesType := msgp.NextType(entriesFirstByte) + // We have to read out the entries raw all the way first because we don't + // know whether it is compressed or not until we read the options map which + // comes after. I guess we could use some kind of detection logic to + // determine if it is gzipped by peeking and just ignoring options, but + // this seems simpler for now. + var entriesRaw []byte + switch entriesType { + case msgp.StrType: + var entriesStr string + entriesStr, err = dc.ReadString() + if err != nil { + return msgp.WrapError(err, "EntriesRaw") + } + entriesRaw = []byte(entriesStr) + case msgp.BinType: + entriesRaw, err = dc.ReadBytes(nil) + if err != nil { + return msgp.WrapError(err, "EntriesRaw") + } + default: + return msgp.WrapError(fmt.Errorf("invalid type %d", entriesType), "EntriesRaw") + } + + if arrLen == 3 { + pfe.OptionsMap, err = parseOptions(dc) + if err != nil { + return err + } + } + + err = pfe.parseEntries(entriesRaw, pfe.Compressed() == "gzip", tag) + if err != nil { + return err + } + + return nil +} + +func (pfe *PackedForwardEventLogRecords) parseEntries(entriesRaw []byte, isGzipped bool, tag string) error { + var reader io.Reader + reader = bytes.NewReader(entriesRaw) + + if isGzipped { + var err error + reader, err = gzip.NewReader(reader) + if err != nil { + return err + } + defer reader.(*gzip.Reader).Close() + } + + msgpReader := msgp.NewReader(reader) + for { + lr := pdata.NewLogRecord() + err := parseEntryToLogRecord(msgpReader, lr) + if err != nil { + if msgp.Cause(err) == io.EOF { + return nil + } + return err + } + + lr.Attributes().InsertString(tagAttributeKey, tag) + + pfe.LogSlice.Append(lr) + } +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/conversion_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/conversion_test.go new file mode 100644 index 00000000000..5681895c272 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/conversion_test.go @@ -0,0 +1,213 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tinylib/msgp/msgp" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testutil/logstest" +) + +func TestMessageEventConversion(t *testing.T) { + eventBytes := parseHexDump("testdata/message-event") + reader := msgp.NewReader(bytes.NewReader(eventBytes)) + + var event MessageEventLogRecord + err := event.DecodeMsg(reader) + require.Nil(t, err) + + le := event.LogRecords().At(0) + le.Attributes().Sort() + + expected := logstest.Logs( + logstest.Log{ + Timestamp: 1593031012000000000, + Body: pdata.NewAttributeValueString("..."), + Attributes: map[string]pdata.AttributeValue{ + "container_id": pdata.NewAttributeValueString("b00a67eb645849d6ab38ff8beb4aad035cc7e917bf123c3e9057c7e89fc73d2d"), + "container_name": pdata.NewAttributeValueString("/unruffled_cannon"), + "fluent.tag": pdata.NewAttributeValueString("b00a67eb6458"), + "source": pdata.NewAttributeValueString("stdout"), + }, + }, + ) + require.EqualValues(t, expected.ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().At(0), le) +} + +func TestAttributeTypeConversion(t *testing.T) { + + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, "my-tag") + b = msgp.AppendInt(b, 5000) + b = msgp.AppendMapHeader(b, 14) + b = msgp.AppendString(b, "a") + b = msgp.AppendFloat64(b, 5.0) + b = msgp.AppendString(b, "b") + b = msgp.AppendFloat32(b, 6.0) + b = msgp.AppendString(b, "c") + b = msgp.AppendBool(b, true) + b = msgp.AppendString(b, "d") + b = msgp.AppendInt8(b, 1) + b = msgp.AppendString(b, "e") + b = msgp.AppendInt16(b, 2) + b = msgp.AppendString(b, "f") + b = msgp.AppendInt32(b, 3) + b = msgp.AppendString(b, "g") + b = msgp.AppendInt64(b, 4) + b = msgp.AppendString(b, "h") + b = msgp.AppendUint8(b, ^uint8(0)) + b = msgp.AppendString(b, "i") + b = msgp.AppendUint16(b, ^uint16(0)) + b = msgp.AppendString(b, "j") + b = msgp.AppendUint32(b, ^uint32(0)) + b = msgp.AppendString(b, "k") + b = msgp.AppendUint64(b, ^uint64(0)) + b = msgp.AppendString(b, "l") + b = msgp.AppendComplex64(b, complex64(0)) + b = msgp.AppendString(b, "m") + b = msgp.AppendBytes(b, []byte{0x1, 0x65, 0x2}) + b = msgp.AppendString(b, "n") + b = msgp.AppendArrayHeader(b, 2) + b = msgp.AppendString(b, "first") + b = msgp.AppendString(b, "second") + + reader := msgp.NewReader(bytes.NewReader(b)) + + var event MessageEventLogRecord + err := event.DecodeMsg(reader) + require.Nil(t, err) + + le := event.LogRecords().At(0) + le.Attributes().Sort() + require.EqualValues(t, logstest.Logs( + logstest.Log{ + Timestamp: 5000000000000, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "a": pdata.NewAttributeValueDouble(5.0), + "b": pdata.NewAttributeValueDouble(6.0), + "c": pdata.NewAttributeValueBool(true), + "d": pdata.NewAttributeValueInt(1), + "e": pdata.NewAttributeValueInt(2), + "f": pdata.NewAttributeValueInt(3), + "fluent.tag": pdata.NewAttributeValueString("my-tag"), + "g": pdata.NewAttributeValueInt(4), + "h": pdata.NewAttributeValueInt(255), + "i": pdata.NewAttributeValueInt(65535), + "j": pdata.NewAttributeValueInt(4294967295), + "k": pdata.NewAttributeValueInt(-1), + "l": pdata.NewAttributeValueString("(0+0i)"), + "m": pdata.NewAttributeValueString("\001e\002"), + "n": pdata.NewAttributeValueString(`["first","second"]`), + }, + }, + ).ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().At(0), le) +} + +func TestEventMode(t *testing.T) { + require.Equal(t, "unknown", UnknownMode.String()) + require.Equal(t, "message", MessageMode.String()) + require.Equal(t, "forward", ForwardMode.String()) + require.Equal(t, "packedforward", PackedForwardMode.String()) + + const TestMode EventMode = 6 + require.Panics(t, func() { _ = TestMode.String() }) +} + +func TestTimeFromTimestampBadType(t *testing.T) { + _, err := timeFromTimestamp("bad") + require.NotNil(t, err) +} + +func TestMessageEventConversionWithErrors(t *testing.T) { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, "my-tag") + b = msgp.AppendInt(b, 5000) + b = msgp.AppendMapHeader(b, 1) + b = msgp.AppendString(b, "a") + b = msgp.AppendFloat64(b, 5.0) + + for i := 0; i < len(b)-1; i++ { + t.Run(fmt.Sprintf("EOF at byte %d", i), func(t *testing.T) { + reader := msgp.NewReader(bytes.NewReader(b[:i])) + + var event MessageEventLogRecord + err := event.DecodeMsg(reader) + require.NotNil(t, err) + }) + } + + t.Run("Invalid timestamp type uint", func(t *testing.T) { + in := make([]byte, len(b)) + copy(in, b) + in[8] = 0xcd + reader := msgp.NewReader(bytes.NewReader(in)) + + var event MessageEventLogRecord + err := event.DecodeMsg(reader) + require.NotNil(t, err) + }) +} + +func TestForwardEventConversionWithErrors(t *testing.T) { + b := parseHexDump("testdata/forward-event") + + for i := 0; i < len(b)-1; i++ { + t.Run(fmt.Sprintf("EOF at byte %d", i), func(t *testing.T) { + reader := msgp.NewReader(bytes.NewReader(b[:i])) + + var event ForwardEventLogRecords + err := event.DecodeMsg(reader) + require.NotNil(t, err) + }) + } +} + +func TestPackedForwardEventConversionWithErrors(t *testing.T) { + b := parseHexDump("testdata/forward-packed-compressed") + + for i := 0; i < len(b)-1; i++ { + t.Run(fmt.Sprintf("EOF at byte %d", i), func(t *testing.T) { + reader := msgp.NewReader(bytes.NewReader(b[:i])) + + var event PackedForwardEventLogRecords + err := event.DecodeMsg(reader) + require.NotNil(t, err) + }) + } + + t.Run("Invalid gzip header", func(t *testing.T) { + in := make([]byte, len(b)) + copy(in, b) + in[0x71] = 0xff + reader := msgp.NewReader(bytes.NewReader(in)) + + var event PackedForwardEventLogRecords + err := event.DecodeMsg(reader) + require.NotNil(t, err) + require.Contains(t, err.Error(), "gzip") + print(err.Error()) + }) +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/factory.go b/internal/otel_collector/receiver/fluentforwardreceiver/factory.go new file mode 100644 index 00000000000..5061ad64112 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/factory.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +// This file implements factory for SignalFx receiver. + +const ( + // The value of "type" key in configuration. + typeStr = "fluentforward" +) + +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithLogs(createLogsReceiver)) +} + +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createLogsReceiver( + _ context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + consumer consumer.LogsConsumer, +) (component.LogsReceiver, error) { + + rCfg := cfg.(*Config) + return newFluentReceiver(params.Logger, rCfg, consumer) +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/factory_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/factory_test.go new file mode 100644 index 00000000000..9b6581465c0 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/factory_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.ListenAddress = "localhost:0" // Endpoint is required, not going to be used here. + + require.Equal(t, configmodels.Type("fluentforward"), factory.Type()) + + tReceiver, err := factory.CreateLogsReceiver(context.Background(), component.ReceiverCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewLogsNop()) + assert.Nil(t, err, "receiver creation failed") + assert.NotNil(t, tReceiver, "receiver creation failed") +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/heartbeat.go b/internal/otel_collector/receiver/fluentforwardreceiver/heartbeat.go new file mode 100644 index 00000000000..c9cb68d2c7a --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/heartbeat.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + "net" + "syscall" + + "go.uber.org/zap" +) + +// See https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#heartbeat-message +func respondToHeartbeats(ctx context.Context, udpSock net.PacketConn, logger *zap.Logger) { + go func() { + <-ctx.Done() + udpSock.Close() + }() + + buf := make([]byte, 1) + for { + n, addr, err := udpSock.ReadFrom(buf) + if err != nil || n == 0 { + if ctx.Err() != nil || err == syscall.EINVAL { + return + } + continue + } + // Technically the heartbeat should be a byte 0x00 but just echo back + // whatever the client sent and move on. + _, err = udpSock.WriteTo(buf, addr) + if err != nil { + logger.Debug("Failed to write back heartbeat packet", zap.String("addr", addr.String()), zap.Error(err)) + } + } +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/heartbeat_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/heartbeat_test.go new file mode 100644 index 00000000000..8d73803a1ea --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/heartbeat_test.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func TestUDPHeartbeat(t *testing.T) { + udpSock, err := net.ListenPacket("udp", "127.0.0.1:0") + require.Nil(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go respondToHeartbeats(ctx, udpSock, zap.NewNop()) + + conn, err := net.Dial("udp", udpSock.LocalAddr().String()) + require.Nil(t, err) + + n, err := conn.Write([]byte{0x00}) + require.Nil(t, err) + require.Equal(t, 1, n) + + buf := make([]byte, 1) + require.NoError(t, conn.SetReadDeadline(time.Now().Add(5*time.Second))) + n, err = conn.Read(buf) + require.Nil(t, err) + require.Equal(t, 1, n) + require.Equal(t, uint8(0x00), buf[0]) +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics.go b/internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics.go new file mode 100644 index 00000000000..40dad5e1fec --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics.go @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// package observ contains logic pertaining to the internal observation +// of the fluent forward receiver. +package observ + +import ( + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + ConnectionsOpened = stats.Int64( + "fluent_opened_connections", + "Number of connections opened to the fluentforward receiver", + stats.UnitDimensionless) + connectionsOpenedView = &view.View{ + Name: ConnectionsOpened.Name(), + Measure: ConnectionsOpened, + Description: ConnectionsOpened.Description(), + Aggregation: view.Sum(), + } + + ConnectionsClosed = stats.Int64( + "fluent_closed_connections", + "Number of connections closed to the fluentforward receiver", + stats.UnitDimensionless) + connectionsClosedView = &view.View{ + Name: ConnectionsClosed.Name(), + Measure: ConnectionsClosed, + Description: ConnectionsClosed.Description(), + Aggregation: view.Sum(), + } + + EventsParsed = stats.Int64( + "fluent_events_parsed", + "Number of Fluent events parsed successfully", + stats.UnitDimensionless) + eventsParsedView = &view.View{ + Name: EventsParsed.Name(), + Measure: EventsParsed, + Description: EventsParsed.Description(), + Aggregation: view.Sum(), + } + + FailedToParse = stats.Int64( + "fluent_parse_failures", + "Number of times Fluent messages failed to be decoded", + stats.UnitDimensionless) + failedToParseView = &view.View{ + Name: FailedToParse.Name(), + Measure: FailedToParse, + Description: FailedToParse.Description(), + Aggregation: view.Sum(), + } + + RecordsGenerated = stats.Int64( + "fluent_records_generated", + "Number of log records generated from Fluent forward input", + stats.UnitDimensionless) + recordsGeneratedView = &view.View{ + Name: RecordsGenerated.Name(), + Measure: RecordsGenerated, + Description: RecordsGenerated.Description(), + Aggregation: view.Sum(), + } +) + +func MetricViews() []*view.View { + return []*view.View{ + connectionsOpenedView, + connectionsClosedView, + eventsParsedView, + failedToParseView, + recordsGeneratedView, + } +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics_test.go new file mode 100644 index 00000000000..11d1ce9e454 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/observ/metrics_test.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package observ + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestViews(t *testing.T) { + require.Equal(t, len(MetricViews()), 5) +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/parse_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/parse_test.go new file mode 100644 index 00000000000..04afcd39b65 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/parse_test.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "encoding/hex" + "io/ioutil" + "path/filepath" + "runtime" + "strings" +) + +func parseHexDump(name string) []byte { + _, file, _, _ := runtime.Caller(0) + dir, err := filepath.Abs(filepath.Dir(file)) + if err != nil { + panic("Failed to find absolute path of hex dump: " + err.Error()) + } + + path := filepath.Join(dir, name+".hexdump") + dump, err := ioutil.ReadFile(path) + if err != nil { + panic("failed to read hex dump file " + path + ": " + err.Error()) + } + + var hexStr string + for _, line := range strings.Split(string(dump), "\n") { + if len(line) == 0 { + continue + } + line = strings.Split(line, "|")[0] + hexStr += strings.Join(strings.Fields(line)[1:], "") + } + + bytes, err := hex.DecodeString(hexStr) + if err != nil { + panic("failed to parse hex bytes: " + err.Error()) + } + + return bytes +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/receiver.go b/internal/otel_collector/receiver/fluentforwardreceiver/receiver.go new file mode 100644 index 00000000000..3b110baac8d --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/receiver.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + "net" + "strings" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" +) + +// Give the event channel a bit of buffer to help reduce backpressure on +// FluentBit and increase throughput. +const eventChannelLength = 100 + +type fluentReceiver struct { + collector *Collector + listener net.Listener + conf *Config + logger *zap.Logger + server *server + cancel context.CancelFunc +} + +func newFluentReceiver(logger *zap.Logger, conf *Config, next consumer.LogsConsumer) (component.LogsReceiver, error) { + eventCh := make(chan Event, eventChannelLength) + + collector := newCollector(eventCh, next, logger) + + server := newServer(eventCh, logger) + + return &fluentReceiver{ + collector: collector, + server: server, + conf: conf, + logger: logger, + }, nil +} + +func (r *fluentReceiver) Start(ctx context.Context, _ component.Host) error { + receiverCtx, cancel := context.WithCancel(ctx) + r.cancel = cancel + + r.collector.Start(receiverCtx) + + listenAddr := r.conf.ListenAddress + + var listener net.Listener + var udpListener net.PacketConn + var err error + if strings.HasPrefix(listenAddr, "/") || strings.HasPrefix(listenAddr, "unix://") { + listener, err = net.Listen("unix", strings.TrimPrefix(listenAddr, "unix://")) + } else { + listener, err = net.Listen("tcp", listenAddr) + if err == nil { + udpListener, err = net.ListenPacket("udp", listenAddr) + } + } + + if err != nil { + return err + } + + r.listener = listener + + r.server.Start(receiverCtx, listener) + + if udpListener != nil { + go respondToHeartbeats(receiverCtx, udpListener, r.logger) + } + + return nil +} + +func (r *fluentReceiver) Shutdown(context.Context) error { + r.cancel() + return nil +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/receiver_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/receiver_test.go new file mode 100644 index 00000000000..a7122825f1f --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/receiver_test.go @@ -0,0 +1,438 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/tinylib/msgp/msgp" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testutil/logstest" +) + +func setupServer(t *testing.T) (func() net.Conn, *consumertest.LogsSink, *observer.ObservedLogs, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + + next := new(consumertest.LogsSink) + logCore, logObserver := observer.New(zap.DebugLevel) + logger := zap.New(logCore) + + conf := &Config{ + ListenAddress: "127.0.0.1:0", + } + + receiver, err := newFluentReceiver(logger, conf, next) + require.NoError(t, err) + require.NoError(t, receiver.Start(ctx, nil)) + + connect := func() net.Conn { + conn, err := net.Dial("tcp", receiver.(*fluentReceiver).listener.Addr().String()) + require.Nil(t, err) + return conn + } + + go func() { + <-ctx.Done() + require.NoError(t, receiver.Shutdown(ctx)) + }() + + return connect, next, logObserver, cancel +} + +func waitForConnectionClose(t *testing.T, conn net.Conn) { + one := make([]byte, 1) + require.NoError(t, conn.SetReadDeadline(time.Now().Add(5*time.Second))) + _, err := conn.Read(one) + // If this is a timeout, then the connection didn't actually close like + // expected. + require.Equal(t, io.EOF, err) +} + +// Make sure malformed events don't cause panics. +func TestMessageEventConversionMalformed(t *testing.T) { + connect, _, observedLogs, cancel := setupServer(t) + defer cancel() + + eventBytes := parseHexDump("testdata/message-event") + + vulnerableBits := []int{0, 1, 14, 59} + + for _, pos := range vulnerableBits { + eventBytes[pos]++ + + conn := connect() + n, err := conn.Write(eventBytes) + require.NoError(t, err) + require.Len(t, eventBytes, n) + + waitForConnectionClose(t, conn) + + require.Len(t, observedLogs.FilterMessageSnippet("Unexpected").All(), 1) + _ = observedLogs.TakeAll() + } +} + +func TestMessageEvent(t *testing.T) { + connect, next, _, cancel := setupServer(t) + defer cancel() + + eventBytes := parseHexDump("testdata/message-event") + + conn := connect() + n, err := conn.Write(eventBytes) + require.NoError(t, err) + require.Equal(t, len(eventBytes), n) + require.NoError(t, conn.Close()) + + var converted []pdata.Logs + require.Eventually(t, func() bool { + converted = next.AllLogs() + return len(converted) == 1 + }, 5*time.Second, 10*time.Millisecond) + + converted[0].ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().At(0).Attributes().Sort() + require.EqualValues(t, logstest.Logs(logstest.Log{ + Timestamp: 1593031012000000000, + Body: pdata.NewAttributeValueString("..."), + Attributes: map[string]pdata.AttributeValue{ + "container_id": pdata.NewAttributeValueString("b00a67eb645849d6ab38ff8beb4aad035cc7e917bf123c3e9057c7e89fc73d2d"), + "container_name": pdata.NewAttributeValueString("/unruffled_cannon"), + "fluent.tag": pdata.NewAttributeValueString("b00a67eb6458"), + "source": pdata.NewAttributeValueString("stdout"), + }, + }, + ), converted[0]) +} + +func TestForwardEvent(t *testing.T) { + connect, next, _, cancel := setupServer(t) + defer cancel() + + eventBytes := parseHexDump("testdata/forward-event") + + conn := connect() + n, err := conn.Write(eventBytes) + require.NoError(t, err) + require.Equal(t, len(eventBytes), n) + require.NoError(t, conn.Close()) + + var converted []pdata.Logs + require.Eventually(t, func() bool { + converted = next.AllLogs() + return len(converted) == 1 + }, 5*time.Second, 10*time.Millisecond) + + ls := converted[0].ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs() + ls.At(0).Attributes().Sort() + ls.At(1).Attributes().Sort() + require.EqualValues(t, logstest.Logs( + logstest.Log{ + Timestamp: 1593032377776693638, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "Mem.free": pdata.NewAttributeValueInt(848908), + "Mem.total": pdata.NewAttributeValueInt(7155496), + "Mem.used": pdata.NewAttributeValueInt(6306588), + "Swap.free": pdata.NewAttributeValueInt(0), + "Swap.total": pdata.NewAttributeValueInt(0), + "Swap.used": pdata.NewAttributeValueInt(0), + "fluent.tag": pdata.NewAttributeValueString("mem.0"), + }, + }, + logstest.Log{ + Timestamp: 1593032378756829346, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "Mem.free": pdata.NewAttributeValueInt(848908), + "Mem.total": pdata.NewAttributeValueInt(7155496), + "Mem.used": pdata.NewAttributeValueInt(6306588), + "Swap.free": pdata.NewAttributeValueInt(0), + "Swap.total": pdata.NewAttributeValueInt(0), + "Swap.used": pdata.NewAttributeValueInt(0), + "fluent.tag": pdata.NewAttributeValueString("mem.0"), + }, + }, + ), converted[0]) +} + +func TestEventAcknowledgment(t *testing.T) { + connect, _, logs, cancel := setupServer(t) + defer func() { fmt.Printf("%v", logs.All()) }() + defer cancel() + + const chunkValue = "abcdef01234576789" + + var b []byte + + // Make a message event with the chunk option + b = msgp.AppendArrayHeader(b, 4) + b = msgp.AppendString(b, "my-tag") + b = msgp.AppendInt(b, 5000) + b = msgp.AppendMapHeader(b, 1) + b = msgp.AppendString(b, "a") + b = msgp.AppendFloat64(b, 5.0) + b = msgp.AppendMapStrStr(b, map[string]string{"chunk": chunkValue}) + + conn := connect() + n, err := conn.Write(b) + require.NoError(t, err) + require.Equal(t, len(b), n) + + require.NoError(t, conn.SetReadDeadline(time.Now().Add(5*time.Second))) + resp := map[string]interface{}{} + err = msgp.NewReader(conn).ReadMapStrIntf(resp) + require.NoError(t, err) + + require.Equal(t, chunkValue, resp["ack"]) +} + +func TestForwardPackedEvent(t *testing.T) { + connect, next, _, cancel := setupServer(t) + defer cancel() + + eventBytes := parseHexDump("testdata/forward-packed") + + conn := connect() + n, err := conn.Write(eventBytes) + require.NoError(t, err) + require.Equal(t, len(eventBytes), n) + require.NoError(t, conn.Close()) + + var converted []pdata.Logs + require.Eventually(t, func() bool { + converted = next.AllLogs() + return len(converted) == 1 + }, 5*time.Second, 10*time.Millisecond) + + ls := converted[0].ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs() + for i := 0; i < ls.Len(); i++ { + ls.At(i).Attributes().Sort() + } + require.EqualValues(t, logstest.Logs( + logstest.Log{ + Timestamp: 1593032517024597622, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("starting fluentd worker pid=17 ppid=7 worker=0"), + "pid": pdata.NewAttributeValueInt(17), + "ppid": pdata.NewAttributeValueInt(7), + "worker": pdata.NewAttributeValueInt(0), + }, + }, + logstest.Log{ + Timestamp: 1593032517028573686, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("delayed_commit_timeout is overwritten by ack_response_timeout"), + }, + }, + logstest.Log{ + Timestamp: 1593032517028815948, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("following tail of /var/log/kern.log"), + }, + }, + logstest.Log{ + Timestamp: 1593032517031174229, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("fluentd worker is now running worker=0"), + "worker": pdata.NewAttributeValueInt(0), + }, + }, + logstest.Log{ + Timestamp: 1593032522187382822, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("fluentd worker is now stopping worker=0"), + "worker": pdata.NewAttributeValueInt(0), + }, + }, + ), converted[0]) +} + +func TestForwardPackedCompressedEvent(t *testing.T) { + connect, next, _, cancel := setupServer(t) + defer cancel() + + eventBytes := parseHexDump("testdata/forward-packed-compressed") + + conn := connect() + n, err := conn.Write(eventBytes) + require.NoError(t, err) + require.Equal(t, len(eventBytes), n) + require.NoError(t, conn.Close()) + + var converted []pdata.Logs + require.Eventually(t, func() bool { + converted = next.AllLogs() + return len(converted) == 1 + }, 5*time.Second, 10*time.Millisecond) + + ls := converted[0].ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs() + for i := 0; i < ls.Len(); i++ { + ls.At(i).Attributes().Sort() + } + require.EqualValues(t, logstest.Logs( + logstest.Log{ + Timestamp: 1593032426012197420, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("starting fluentd worker pid=17 ppid=7 worker=0"), + "pid": pdata.NewAttributeValueInt(17), + "ppid": pdata.NewAttributeValueInt(7), + "worker": pdata.NewAttributeValueInt(0), + }, + }, + logstest.Log{ + Timestamp: 1593032426013724933, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("delayed_commit_timeout is overwritten by ack_response_timeout"), + }, + }, + logstest.Log{ + Timestamp: 1593032426020510455, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("following tail of /var/log/kern.log"), + }, + }, + logstest.Log{ + Timestamp: 1593032426024346580, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("fluentd worker is now running worker=0"), + "worker": pdata.NewAttributeValueInt(0), + }, + }, + logstest.Log{ + Timestamp: 1593032434346935532, + Body: pdata.NewAttributeValueNull(), + Attributes: map[string]pdata.AttributeValue{ + "fluent.tag": pdata.NewAttributeValueString("fluent.info"), + "message": pdata.NewAttributeValueString("fluentd worker is now stopping worker=0"), + "worker": pdata.NewAttributeValueInt(0), + }, + }, + ), converted[0]) +} + +func TestUnixEndpoint(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + next := new(consumertest.LogsSink) + + tmpdir, err := ioutil.TempDir("", "fluent-socket") + require.NoError(t, err) + + defer os.RemoveAll(tmpdir) + + conf := &Config{ + ListenAddress: "unix://" + filepath.Join(tmpdir, "fluent.sock"), + } + + receiver, err := newFluentReceiver(zap.NewNop(), conf, next) + require.NoError(t, err) + require.NoError(t, receiver.Start(ctx, nil)) + + conn, err := net.Dial("unix", receiver.(*fluentReceiver).listener.Addr().String()) + require.NoError(t, err) + + n, err := conn.Write(parseHexDump("testdata/message-event")) + require.NoError(t, err) + require.Greater(t, n, 0) + + var converted []pdata.Logs + require.Eventually(t, func() bool { + converted = next.AllLogs() + return len(converted) == 1 + }, 5*time.Second, 10*time.Millisecond) +} + +func makeSampleEvent(tag string) []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, tag) + b = msgp.AppendInt(b, 5000) + b = msgp.AppendMapHeader(b, 1) + b = msgp.AppendString(b, "a") + b = msgp.AppendFloat64(b, 5.0) + return b +} + +func TestHighVolume(t *testing.T) { + connect, next, _, cancel := setupServer(t) + defer cancel() + + const totalRoutines = 8 + const totalMessagesPerRoutine = 1000 + + var wg sync.WaitGroup + for i := 0; i < totalRoutines; i++ { + wg.Add(1) + go func(num int) { + conn := connect() + for j := 0; j < totalMessagesPerRoutine; j++ { + eventBytes := makeSampleEvent(fmt.Sprintf("tag-%d-%d", num, j)) + n, err := conn.Write(eventBytes) + require.NoError(t, err) + require.Equal(t, len(eventBytes), n) + } + require.NoError(t, conn.Close()) + wg.Done() + }(i) + } + + wg.Wait() + + var converted []pdata.Logs + require.Eventually(t, func() bool { + converted = next.AllLogs() + + var total int + for i := range converted { + total += converted[i].LogRecordCount() + } + + return total == totalRoutines*totalMessagesPerRoutine + }, 10*time.Second, 100*time.Millisecond) +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/server.go b/internal/otel_collector/receiver/fluentforwardreceiver/server.go new file mode 100644 index 00000000000..508e8f20f8e --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/server.go @@ -0,0 +1,207 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" + + "github.com/tinylib/msgp/msgp" + "go.opencensus.io/stats" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/fluentforwardreceiver/observ" +) + +// The initial size of the read buffer. Messages can come in that are bigger +// than this, but this serves as a starting point. +const readBufferSize = 10 * 1024 + +type server struct { + outCh chan<- Event + logger *zap.Logger +} + +func newServer(outCh chan<- Event, logger *zap.Logger) *server { + return &server{ + outCh: outCh, + logger: logger, + } +} + +func (s *server) Start(ctx context.Context, listener net.Listener) { + go func() { + s.handleConnections(ctx, listener) + if ctx.Err() == nil { + panic("logic error in receiver, connections should always be listened for while receiver is running") + } + }() +} + +func (s *server) handleConnections(ctx context.Context, listener net.Listener) { + for { + conn, err := listener.Accept() + if ctx.Err() != nil { + return + } + // If there is an error and the receiver isn't shutdown, we need to + // keep trying to accept connections if at all possible. Put in a sleep + // to prevent hot loops in case the error persists. + if err != nil { + time.Sleep(10 * time.Second) + continue + } + stats.Record(ctx, observ.ConnectionsOpened.M(1)) + + s.logger.Debug("Got connection", zap.String("remoteAddr", conn.RemoteAddr().String())) + + go func() { + defer stats.Record(ctx, observ.ConnectionsClosed.M(1)) + + err := s.handleConn(ctx, conn) + if err != nil { + if err == io.EOF { + s.logger.Debug("Closing connection", zap.String("remoteAddr", conn.RemoteAddr().String()), zap.Error(err)) + } else { + s.logger.Debug("Unexpected error handling connection", zap.String("remoteAddr", conn.RemoteAddr().String()), zap.Error(err)) + } + } + conn.Close() + }() + } +} + +func (s *server) handleConn(ctx context.Context, conn net.Conn) error { + reader := msgp.NewReaderSize(conn, readBufferSize) + + for { + mode, err := DetermineNextEventMode(reader.R) + if err != nil { + return err + } + + var event Event + switch mode { + case UnknownMode: + return errors.New("could not determine event mode") + case MessageMode: + event = &MessageEventLogRecord{} + case ForwardMode: + event = &ForwardEventLogRecords{} + case PackedForwardMode: + event = &PackedForwardEventLogRecords{} + default: + panic("programmer bug in mode handling") + } + + err = event.DecodeMsg(reader) + if err != nil { + if err != io.EOF { + stats.Record(ctx, observ.FailedToParse.M(1)) + } + return fmt.Errorf("failed to parse %s mode event: %v", mode.String(), err) + } + + stats.Record(ctx, observ.EventsParsed.M(1)) + + s.outCh <- event + + // We must acknowledge the 'chunk' option if given. We could do this in + // another goroutine if it is too much of a bottleneck to reading + // messages -- this is the only thing that sends data back to the + // client. + if event.Chunk() != "" { + err := msgp.Encode(conn, AckResponse{Ack: event.Chunk()}) + if err != nil { + return fmt.Errorf("failed to acknowledge chunk %s: %v", event.Chunk(), err) + } + } + } +} + +// DetermineNextEventMode inspects the next bit of data from the given peeker +// reader to determine which type of event mode it is. According to the +// forward protocol spec: "Server MUST detect the carrier mode by inspecting +// the second element of the array." It is assumed that peeker is aligned at +// the start of a new event, otherwise the result is undefined and will +// probably error. +func DetermineNextEventMode(peeker Peeker) (EventMode, error) { + var chunk []byte + var err error + chunk, err = peeker.Peek(2) + if err != nil { + return UnknownMode, err + } + + // The first byte is the array header, which will always be 1 byte since no + // message modes have more than 4 entries. So skip to the second byte which + // is the tag string header. + tagType := chunk[1] + // We already read the first type for the type + tagLen := 1 + + isFixStr := tagType&0b10100000 == 0b10100000 + if isFixStr { + tagLen += int(tagType & 0b00011111) + } else { + switch tagType { + case 0xd9: + chunk, err = peeker.Peek(3) + if err != nil { + return UnknownMode, err + } + tagLen += 1 + int(chunk[2]) + case 0xda: + chunk, err = peeker.Peek(4) + if err != nil { + return UnknownMode, err + } + tagLen += 2 + int(binary.BigEndian.Uint16(chunk[2:])) + case 0xdb: + chunk, err = peeker.Peek(6) + if err != nil { + return UnknownMode, err + } + tagLen += 4 + int(binary.BigEndian.Uint32(chunk[2:])) + default: + return UnknownMode, errors.New("malformed tag field") + } + } + + // Skip past the first byte (array header) and the entire tag and then get + // one byte into the second field -- that is enough to know its type. + chunk, err = peeker.Peek(1 + tagLen + 1) + if err != nil { + return UnknownMode, err + } + + secondElmType := msgp.NextType(chunk[1+tagLen:]) + + switch secondElmType { + case msgp.IntType, msgp.UintType, msgp.ExtensionType: + return MessageMode, nil + case msgp.ArrayType: + return ForwardMode, nil + case msgp.BinType, msgp.StrType: + return PackedForwardMode, nil + default: + return UnknownMode, fmt.Errorf("unable to determine next event mode for type %v", secondElmType) + } +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/server_test.go b/internal/otel_collector/receiver/fluentforwardreceiver/server_test.go new file mode 100644 index 00000000000..e406d0502a9 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/server_test.go @@ -0,0 +1,127 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fluentforwardreceiver + +import ( + "bufio" + "bytes" + "errors" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tinylib/msgp/msgp" +) + +func TestDetermineNextEventMode(t *testing.T) { + cases := []struct { + name string + event func() []byte + expectedMode EventMode + expectedError error + }{ + { + "basic", + func() []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, "my-tag") + b = msgp.AppendInt(b, 5000) + return b + }, + MessageMode, + nil, + }, + { + "str8-tag", + func() []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, strings.Repeat("a", 128)) + b = msgp.AppendInt(b, 5000) + return b + }, + MessageMode, + nil, + }, + { + "str16-tag", + func() []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, strings.Repeat("a", 1024)) + b = msgp.AppendInt(b, 5000) + return b + }, + MessageMode, + nil, + }, + { + "str32-tag", + func() []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, strings.Repeat("a", 66000)) + b = msgp.AppendInt(b, 5000) + return b + }, + MessageMode, + nil, + }, + { + "non-string-tag", + func() []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendInt(b, 10) + b = msgp.AppendInt(b, 5000) + return b + }, + UnknownMode, + errors.New("malformed tag field"), + }, + { + "float-second-elm", + func() []byte { + var b []byte + + b = msgp.AppendArrayHeader(b, 3) + b = msgp.AppendString(b, "my-tag") + b = msgp.AppendFloat64(b, 5000.0) + return b + }, + UnknownMode, + errors.New("unable to determine next event mode for type float64"), + }, + } + + for i := range cases { + c := cases[i] + t.Run(c.name, func(t *testing.T) { + peeker := bufio.NewReaderSize(bytes.NewReader(c.event()), 1024*100) + mode, err := DetermineNextEventMode(peeker) + if c.expectedError != nil { + require.Equal(t, c.expectedError, err) + } else { + require.Equal(t, c.expectedMode, mode) + } + }) + } +} diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/testdata/config.yaml b/internal/otel_collector/receiver/fluentforwardreceiver/testdata/config.yaml new file mode 100644 index 00000000000..863f73db801 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/testdata/config.yaml @@ -0,0 +1,15 @@ +receivers: + fluentforward: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + logs: + receivers: [fluentforward] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-event.hexdump b/internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-event.hexdump new file mode 100644 index 00000000000..eafe3fec810 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-event.hexdump @@ -0,0 +1,12 @@ +00000000 92 a5 6d 65 6d 2e 30 92 92 d7 00 5e f3 be b9 2e |..mem.0....^....| +00000010 4b 67 86 86 a9 4d 65 6d 2e 74 6f 74 61 6c ce 00 |Kg...Mem.total..| +00000020 6d 2f 28 a8 4d 65 6d 2e 75 73 65 64 ce 00 60 3b |m/(.Mem.used..`;| +00000030 1c a8 4d 65 6d 2e 66 72 65 65 ce 00 0c f4 0c aa |..Mem.free......| +00000040 53 77 61 70 2e 74 6f 74 61 6c 00 a9 53 77 61 70 |Swap.total..Swap| +00000050 2e 75 73 65 64 00 a9 53 77 61 70 2e 66 72 65 65 |.used..Swap.free| +00000060 00 92 d7 00 5e f3 be ba 2d 1c 4c a2 86 a9 4d 65 |....^...-.L...Me| +00000070 6d 2e 74 6f 74 61 6c ce 00 6d 2f 28 a8 4d 65 6d |m.total..m/(.Mem| +00000080 2e 75 73 65 64 ce 00 60 3b 1c a8 4d 65 6d 2e 66 |.used..`;..Mem.f| +00000090 72 65 65 ce 00 0c f4 0c aa 53 77 61 70 2e 74 6f |ree......Swap.to| +000000a0 74 61 6c 00 a9 53 77 61 70 2e 75 73 65 64 00 a9 |tal..Swap.used..| +000000b0 53 77 61 70 2e 66 72 65 65 00 |Swap.free. | diff --git a/internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-packed-compressed.hexdump b/internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-packed-compressed.hexdump new file mode 100644 index 00000000000..4ae344bbae3 --- /dev/null +++ b/internal/otel_collector/receiver/fluentforwardreceiver/testdata/forward-packed-compressed.hexdump @@ -0,0 +1,32 @@ +00000000 93 ab 66 6c 75 65 6e 74 2e 69 6e 66 6f db 00 00 |..fluent.info...| +00000010 01 af 1f 8b 08 00 ea be f3 5e 00 03 9b 74 9d 21 |.........^...t.!| +00000020 ee f3 be 57 0c bb e4 74 5a 16 17 64 a6 08 2e 29 |...W...tZ..d...)| +00000030 00 92 ec cb ca f3 8b b2 53 8b 18 96 e7 a6 16 17 |........S.......| +00000040 27 a6 a7 de d4 2b 2e 49 2c 2a c9 cc 4b 57 48 cb |'....+.I,*..KWH.| +00000050 29 4d cd 2b 49 51 80 a8 50 00 aa b6 35 34 57 00 |)M.+IQ..P...54W.| +00000060 e9 b2 35 87 0a da 1a 00 00 ed 57 34 22 57 00 00 |..5.......W4"W..| +00000070 00 1f 8b 08 00 ea be f3 5e 00 03 9b 74 9d 21 ee |........^...t.!.| +00000080 f3 be 57 0c 17 73 59 1b 97 e7 a6 16 17 27 a6 a7 |..W..sY......'..| +00000090 de b4 4d 49 cd 49 ac 4c 4d 89 4f ce cf cd cd 2c |..MI.I.LM.O....,| +000000a0 89 2f c9 cc 4d cd 2f 2d 51 c8 2c 56 c8 2f 4b 2d |./..M./-Q.,V./K-| +000000b0 2a 2f ca 2c 29 49 cd 53 48 aa 54 48 4c ce 8e 2f |*/.,)I.SH.THL../| +000000c0 4a 2d 2e c8 cf 2b 4e 85 29 03 00 4b 69 57 f4 53 |J-...+N.)..KiW.S| +000000d0 00 00 00 1f 8b 08 00 ea be f3 5e 00 03 9b 74 9d |..........^...t.| +000000e0 21 ee f3 be 57 8c 16 df be 37 2e cf 4d 2d 2e 4e |!...W....7..M-.N| +000000f0 4c 4f bd a9 9c 96 9f 93 93 5f 9e 99 97 ae 50 92 |LO......._....P.| +00000100 98 99 a3 90 9f a6 a0 5f 96 58 a4 9f 93 9f ae 9f |......._.X......| +00000110 9d 5a 94 a7 07 64 00 00 3c 6b b5 13 39 00 00 00 |.Z...d.. # default = 1m + scrapers: + : + : + ... +``` + +The available scrapers are: + +| Scraper | Supported OSs | Description | +|------------|------------------------------|--------------------------------------------------------| +| cpu | All except Mac[1] | CPU utilization metrics | +| disk | All except Mac[1] | Disk I/O metrics | +| load | All | CPU load metrics | +| filesystem | All | File System utilization metrics | +| memory | All | Memory utilization metrics | +| network | All | Network interface I/O metrics & TCP connection metrics | +| processes | Linux | Process count metrics | +| swap | All | Swap space utilization and I/O metrics | +| process | Linux & Windows | Per process CPU, Memory, and Disk I/O metrics | + +### Notes + +[1] Not supported on Mac when compiled without cgo which is the default. + +Several scrapers support additional configuration: + +### Disk + +```yaml +disk: + : + devices: [ , ... ] + match_type: +``` + +### File System + +```yaml +filesystem: + : + devices: [ , ... ] + match_type: + : + fs_types: [ , ... ] + match_type: + : + mount_points: [ , ... ] + match_type: +``` + +### Network + +```yaml +network: + : + interfaces: [ , ... ] + match_type: +``` + +### Process + +```yaml +process: + disk: + : + names: [ , ... ] + match_type: +``` + +## Advanced Configuration + +### Filtering + +If you are only interested in a subset of metrics from a particular source, +it is recommended you use this receiver with the +[Filter Processor](https://github.com/open-telemetry/opentelemetry-collector/tree/master/processor/filterprocessor). + +### Different Frequencies + +If you would like to scrape some metrics at a different frequency than others, +you can configure multiple `hostmetrics` receivers with different +`collection_interval` values. For example: + +```yaml +receivers: + hostmetrics: + collection_interval: 30s + scrapers: + cpu: + memory: + + hostmetrics/disk: + collection_interval: 1m + scrapers: + disk: + filesystem: + +service: + pipelines: + metrics: + receivers: [hostmetrics, hostmetrics/disk] +``` diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/codegen.go b/internal/otel_collector/receiver/hostmetricsreceiver/codegen.go new file mode 100644 index 00000000000..feefb424aaf --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/codegen.go @@ -0,0 +1,19 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +//go:generate mdatagen metadata.yaml + +package hostmetricsreceiver diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/config.go new file mode 100644 index 00000000000..82626aba81d --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/config.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hostmetricsreceiver + +import ( + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// Config defines configuration for HostMetrics receiver. +type Config struct { + scraperhelper.ScraperControllerSettings `mapstructure:",squash"` + Scrapers map[string]internal.Config `mapstructure:"-"` +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/config_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/config_test.go new file mode 100644 index 00000000000..1b3c3f9d34e --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/config_test.go @@ -0,0 +1,118 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hostmetricsreceiver + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + require.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 2) + + r0 := cfg.Receivers["hostmetrics"] + defaultConfigCPUScraper := factory.CreateDefaultConfig() + defaultConfigCPUScraper.(*Config).Scrapers = map[string]internal.Config{ + cpuscraper.TypeStr: (&cpuscraper.Factory{}).CreateDefaultConfig(), + } + + assert.Equal(t, defaultConfigCPUScraper, r0) + + r1 := cfg.Receivers["hostmetrics/customname"].(*Config) + expectedConfig := &Config{ + ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "hostmetrics/customname", + }, + CollectionInterval: 30 * time.Second, + }, + Scrapers: map[string]internal.Config{ + cpuscraper.TypeStr: &cpuscraper.Config{}, + diskscraper.TypeStr: &diskscraper.Config{}, + loadscraper.TypeStr: &loadscraper.Config{}, + filesystemscraper.TypeStr: &filesystemscraper.Config{}, + memoryscraper.TypeStr: &memoryscraper.Config{}, + networkscraper.TypeStr: &networkscraper.Config{ + Include: networkscraper.MatchConfig{ + Interfaces: []string{"test1"}, + Config: filterset.Config{MatchType: "strict"}, + }, + }, + processesscraper.TypeStr: &processesscraper.Config{}, + swapscraper.TypeStr: &swapscraper.Config{}, + processscraper.TypeStr: &processscraper.Config{ + Include: processscraper.MatchConfig{ + Names: []string{"test2", "test3"}, + Config: filterset.Config{MatchType: "regexp"}, + }, + }, + }, + } + + assert.Equal(t, expectedConfig, r1) +} + +func TestLoadInvalidConfig_NoScrapers(t *testing.T) { + factories, err := componenttest.ExampleComponents() + require.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "config-noscrapers.yaml"), factories) + + require.EqualError(t, err, "error reading receivers configuration for hostmetrics: must specify at least one scraper when using hostmetrics receiver") +} + +func TestLoadInvalidConfig_InvalidScraperKey(t *testing.T) { + factories, err := componenttest.ExampleComponents() + require.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "config-invalidscraperkey.yaml"), factories) + + require.EqualError(t, err, "error reading receivers configuration for hostmetrics: invalid scraper key: invalidscraperkey") +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/example_config.yaml b/internal/otel_collector/receiver/hostmetricsreceiver/example_config.yaml new file mode 100644 index 00000000000..2ee28b7bfd6 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/example_config.yaml @@ -0,0 +1,29 @@ +extensions: + zpages: + endpoint: 0.0.0.0:55679 + +receivers: + hostmetrics: + collection_interval: 1m + scrapers: + cpu: + load: + memory: + disk: + filesystem: + network: + processes: + swap: + +exporters: + logging: + prometheus: + endpoint: 0.0.0.0:8889 + +service: + pipelines: + metrics: + receivers: [hostmetrics] + exporters: [prometheus, logging] + + extensions: [zpages] diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/factory.go new file mode 100644 index 00000000000..41bd6b4fe93 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/factory.go @@ -0,0 +1,223 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hostmetricsreceiver + +import ( + "context" + "errors" + "fmt" + + "github.com/spf13/viper" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper" + "go.opentelemetry.io/collector/receiver/receiverhelper" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for HostMetrics receiver. + +const ( + // The value of "type" key in configuration. + typeStr = "hostmetrics" + scrapersKey = "scrapers" +) + +var ( + scraperFactories = map[string]internal.ScraperFactory{ + cpuscraper.TypeStr: &cpuscraper.Factory{}, + diskscraper.TypeStr: &diskscraper.Factory{}, + loadscraper.TypeStr: &loadscraper.Factory{}, + filesystemscraper.TypeStr: &filesystemscraper.Factory{}, + memoryscraper.TypeStr: &memoryscraper.Factory{}, + networkscraper.TypeStr: &networkscraper.Factory{}, + processesscraper.TypeStr: &processesscraper.Factory{}, + swapscraper.TypeStr: &swapscraper.Factory{}, + } + + resourceScraperFactories = map[string]internal.ResourceScraperFactory{ + processscraper.TypeStr: &processscraper.Factory{}, + } +) + +// NewFactory creates a new factory for host metrics receiver. +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithMetrics(createMetricsReceiver), + receiverhelper.WithCustomUnmarshaler(customUnmarshaler)) +} + +// customUnmarshaler returns custom unmarshaler for this config. +func customUnmarshaler(componentViperSection *viper.Viper, intoCfg interface{}) error { + + // load the non-dynamic config normally + + err := componentViperSection.Unmarshal(intoCfg) + if err != nil { + return err + } + + cfg, ok := intoCfg.(*Config) + if !ok { + return fmt.Errorf("config type not hostmetrics.Config") + } + + // dynamically load the individual collector configs based on the key name + + cfg.Scrapers = map[string]internal.Config{} + + scrapersViperSection, err := config.ViperSubExact(componentViperSection, scrapersKey) + if err != nil { + return err + } + if len(scrapersViperSection.AllKeys()) == 0 { + return errors.New("must specify at least one scraper when using hostmetrics receiver") + } + + for key := range componentViperSection.GetStringMap(scrapersKey) { + factory, ok := getScraperFactory(key) + if !ok { + return fmt.Errorf("invalid scraper key: %s", key) + } + + collectorCfg := factory.CreateDefaultConfig() + collectorViperSection, err := config.ViperSubExact(scrapersViperSection, key) + if err != nil { + return err + } + err = collectorViperSection.UnmarshalExact(collectorCfg) + if err != nil { + return fmt.Errorf("error reading settings for scraper type %q: %v", key, err) + } + + cfg.Scrapers[key] = collectorCfg + } + + return nil +} + +func getScraperFactory(key string) (internal.BaseFactory, bool) { + if factory, ok := scraperFactories[key]; ok { + return factory, true + } + + if factory, ok := resourceScraperFactories[key]; ok { + return factory, true + } + + return nil, false +} + +// createDefaultConfig creates the default configuration for receiver. +func createDefaultConfig() configmodels.Receiver { + return &Config{ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(typeStr)} +} + +// createMetricsReceiver creates a metrics receiver based on provided config. +func createMetricsReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + consumer consumer.MetricsConsumer, +) (component.MetricsReceiver, error) { + oCfg := cfg.(*Config) + + addScraperOptions, err := createAddScraperOptions(ctx, params.Logger, oCfg, scraperFactories, resourceScraperFactories) + if err != nil { + return nil, err + } + + return scraperhelper.NewScraperControllerReceiver( + &oCfg.ScraperControllerSettings, + params.Logger, + consumer, + addScraperOptions..., + ) +} + +func createAddScraperOptions( + ctx context.Context, + logger *zap.Logger, + config *Config, + factories map[string]internal.ScraperFactory, + resourceFactories map[string]internal.ResourceScraperFactory, +) ([]scraperhelper.ScraperControllerOption, error) { + scraperControllerOptions := make([]scraperhelper.ScraperControllerOption, 0, len(config.Scrapers)) + + for key, cfg := range config.Scrapers { + hostMetricsScraper, ok, err := createHostMetricsScraper(ctx, logger, key, cfg, factories) + if err != nil { + return nil, fmt.Errorf("failed to create scraper for key %q: %w", key, err) + } + + if ok { + scraperControllerOptions = append(scraperControllerOptions, scraperhelper.AddMetricsScraper(hostMetricsScraper)) + continue + } + + resourceMetricsScraper, ok, err := createResourceMetricsScraper(ctx, logger, key, cfg, resourceFactories) + if err != nil { + return nil, fmt.Errorf("failed to create resource scraper for key %q: %w", key, err) + } + + if ok { + scraperControllerOptions = append(scraperControllerOptions, scraperhelper.AddResourceMetricsScraper(resourceMetricsScraper)) + continue + } + + return nil, fmt.Errorf("host metrics scraper factory not found for key: %q", key) + } + + return scraperControllerOptions, nil +} + +func createHostMetricsScraper(ctx context.Context, logger *zap.Logger, key string, cfg internal.Config, factories map[string]internal.ScraperFactory) (scraper scraperhelper.MetricsScraper, ok bool, err error) { + factory := factories[key] + if factory == nil { + ok = false + return + } + + ok = true + scraper, err = factory.CreateMetricsScraper(ctx, logger, cfg) + return +} + +func createResourceMetricsScraper(ctx context.Context, logger *zap.Logger, key string, cfg internal.Config, factories map[string]internal.ResourceScraperFactory) (scraper scraperhelper.ResourceMetricsScraper, ok bool, err error) { + factory := factories[key] + if factory == nil { + ok = false + return + } + + ok = true + scraper, err = factory.CreateResourceMetricsScraper(ctx, logger, cfg) + return +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/factory_test.go new file mode 100644 index 00000000000..ebb478bbcef --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/factory_test.go @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hostmetricsreceiver + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +var creationParams = component.ReceiverCreateParams{Logger: zap.NewNop()} + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + tReceiver, err := factory.CreateTracesReceiver(context.Background(), creationParams, cfg, consumertest.NewTracesNop()) + assert.Equal(t, err, configerror.ErrDataTypeIsNotSupported) + assert.Nil(t, tReceiver) + + mReceiver, err := factory.CreateMetricsReceiver(context.Background(), creationParams, cfg, consumertest.NewMetricsNop()) + assert.NoError(t, err) + assert.NotNil(t, mReceiver) + + tLogs, err := factory.CreateLogsReceiver(context.Background(), creationParams, cfg, consumertest.NewLogsNop()) + assert.Equal(t, err, configerror.ErrDataTypeIsNotSupported) + assert.Nil(t, tLogs) +} + +func TestCreateReceiver_ScraperKeyConfigError(t *testing.T) { + const errorKey string = "error" + + factory := NewFactory() + cfg := &Config{Scrapers: map[string]internal.Config{errorKey: &mockConfig{}}} + + _, err := factory.CreateMetricsReceiver(context.Background(), creationParams, cfg, consumertest.NewMetricsNop()) + assert.EqualError(t, err, fmt.Sprintf("host metrics scraper factory not found for key: %q", errorKey)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go new file mode 100644 index 00000000000..c2ebbcc6346 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go @@ -0,0 +1,439 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hostmetricsreceiver + +import ( + "context" + "errors" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +var standardMetrics = []string{ + "system.cpu.time", + "system.memory.usage", + "system.disk.io", + "system.disk.io_time", + "system.disk.ops", + "system.disk.operation_time", + "system.disk.pending_operations", + "system.filesystem.usage", + "system.cpu.load_average.1m", + "system.cpu.load_average.5m", + "system.cpu.load_average.15m", + "system.network.packets", + "system.network.dropped_packets", + "system.network.errors", + "system.network.io", + "system.network.tcp_connections", + "system.swap.paging_ops", + "system.swap.usage", +} + +var resourceMetrics = []string{ + "process.cpu.time", + "process.memory.physical_usage", + "process.memory.virtual_usage", + "process.disk.io", +} + +var systemSpecificMetrics = map[string][]string{ + "linux": {"system.disk.merged", "system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "darwin": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "freebsd": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "openbsd": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "solaris": {"system.filesystem.inodes.usage", "system.swap.page_faults"}, +} + +var factories = map[string]internal.ScraperFactory{ + cpuscraper.TypeStr: &cpuscraper.Factory{}, + diskscraper.TypeStr: &diskscraper.Factory{}, + filesystemscraper.TypeStr: &filesystemscraper.Factory{}, + loadscraper.TypeStr: &loadscraper.Factory{}, + memoryscraper.TypeStr: &memoryscraper.Factory{}, + networkscraper.TypeStr: &networkscraper.Factory{}, + processesscraper.TypeStr: &processesscraper.Factory{}, + swapscraper.TypeStr: &swapscraper.Factory{}, +} + +var resourceFactories = map[string]internal.ResourceScraperFactory{ + processscraper.TypeStr: &processscraper.Factory{}, +} + +func TestGatherMetrics_EndToEnd(t *testing.T) { + scraperFactories = factories + resourceScraperFactories = resourceFactories + + sink := new(consumertest.MetricsSink) + + config := &Config{ + ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ + CollectionInterval: 100 * time.Millisecond, + }, + Scrapers: map[string]internal.Config{ + cpuscraper.TypeStr: &cpuscraper.Config{}, + diskscraper.TypeStr: &diskscraper.Config{}, + filesystemscraper.TypeStr: &filesystemscraper.Config{}, + loadscraper.TypeStr: &loadscraper.Config{}, + memoryscraper.TypeStr: &memoryscraper.Config{}, + networkscraper.TypeStr: &networkscraper.Config{}, + processesscraper.TypeStr: &processesscraper.Config{}, + swapscraper.TypeStr: &swapscraper.Config{}, + }, + } + + if runtime.GOOS == "linux" || runtime.GOOS == "windows" { + config.Scrapers[processscraper.TypeStr] = &processscraper.Config{} + } + + receiver, err := NewFactory().CreateMetricsReceiver(context.Background(), creationParams, config, sink) + + require.NoError(t, err, "Failed to create metrics receiver: %v", err) + + ctx, cancelFn := context.WithCancel(context.Background()) + err = receiver.Start(ctx, componenttest.NewNopHost()) + require.NoError(t, err, "Failed to start metrics receiver: %v", err) + defer func() { assert.NoError(t, receiver.Shutdown(context.Background())) }() + + // canceling the context provided to Start should not cancel any async processes initiated by the receiver + cancelFn() + + const tick = 50 * time.Millisecond + const waitFor = 5 * time.Second + require.Eventuallyf(t, func() bool { + got := sink.AllMetrics() + if len(got) == 0 { + return false + } + + assertIncludesExpectedMetrics(t, got[0]) + return true + }, waitFor, tick, "No metrics were collected after %v", waitFor) +} + +func assertIncludesExpectedMetrics(t *testing.T, got pdata.Metrics) { + // get the superset of metrics returned by all resource metrics (excluding the first) + returnedMetrics := make(map[string]struct{}) + returnedResourceMetrics := make(map[string]struct{}) + rms := got.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + rm := rms.At(i) + metrics := getMetricSlice(t, rm) + returnedMetricNames := getReturnedMetricNames(metrics) + if rm.Resource().Attributes().Len() == 0 { + appendMapInto(returnedMetrics, returnedMetricNames) + } else { + appendMapInto(returnedResourceMetrics, returnedMetricNames) + } + } + + // verify the expected list of metrics returned (os dependent) + expectedMetrics := append(standardMetrics, systemSpecificMetrics[runtime.GOOS]...) + assert.Equal(t, len(expectedMetrics), len(returnedMetrics)) + for _, expected := range expectedMetrics { + assert.Contains(t, returnedMetrics, expected) + } + + // verify the expected list of resource metrics returned (Linux & Windows only) + if runtime.GOOS != "linux" && runtime.GOOS != "windows" { + return + } + + assert.Equal(t, len(resourceMetrics), len(returnedResourceMetrics)) + for _, expected := range resourceMetrics { + assert.Contains(t, returnedResourceMetrics, expected) + } +} + +func getMetricSlice(t *testing.T, rm pdata.ResourceMetrics) pdata.MetricSlice { + ilms := rm.InstrumentationLibraryMetrics() + require.Equal(t, 1, ilms.Len()) + return ilms.At(0).Metrics() +} + +func getReturnedMetricNames(metrics pdata.MetricSlice) map[string]struct{} { + metricNames := make(map[string]struct{}) + for i := 0; i < metrics.Len(); i++ { + metricNames[metrics.At(i).Name()] = struct{}{} + } + return metricNames +} + +func appendMapInto(m1 map[string]struct{}, m2 map[string]struct{}) { + for k, v := range m2 { + m1[k] = v + } +} + +const mockTypeStr = "mock" +const mockResourceTypeStr = "mockresource" + +type mockConfig struct{} + +type mockFactory struct{ mock.Mock } +type mockScraper struct{ mock.Mock } + +func (m *mockFactory) CreateDefaultConfig() internal.Config { return &mockConfig{} } +func (m *mockFactory) CreateMetricsScraper(context.Context, *zap.Logger, internal.Config) (scraperhelper.MetricsScraper, error) { + args := m.MethodCalled("CreateMetricsScraper") + return args.Get(0).(scraperhelper.MetricsScraper), args.Error(1) +} + +func (m *mockScraper) Name() string { return "" } +func (m *mockScraper) Start(context.Context, component.Host) error { return nil } +func (m *mockScraper) Shutdown(context.Context) error { return nil } +func (m *mockScraper) Scrape(context.Context, string) (pdata.MetricSlice, error) { + return pdata.NewMetricSlice(), errors.New("err1") +} + +type mockResourceFactory struct{ mock.Mock } +type mockResourceScraper struct{ mock.Mock } + +func (m *mockResourceFactory) CreateDefaultConfig() internal.Config { return &mockConfig{} } +func (m *mockResourceFactory) CreateResourceMetricsScraper(context.Context, *zap.Logger, internal.Config) (scraperhelper.ResourceMetricsScraper, error) { + args := m.MethodCalled("CreateResourceMetricsScraper") + return args.Get(0).(scraperhelper.ResourceMetricsScraper), args.Error(1) +} + +func (m *mockResourceScraper) Name() string { return "" } +func (m *mockResourceScraper) Start(context.Context, component.Host) error { return nil } +func (m *mockResourceScraper) Shutdown(context.Context) error { return nil } +func (m *mockResourceScraper) Scrape(context.Context, string) (pdata.ResourceMetricsSlice, error) { + return pdata.NewResourceMetricsSlice(), errors.New("err2") +} + +func TestGatherMetrics_ScraperKeyConfigError(t *testing.T) { + scraperFactories = map[string]internal.ScraperFactory{} + resourceScraperFactories = map[string]internal.ResourceScraperFactory{} + + sink := new(consumertest.MetricsSink) + config := &Config{Scrapers: map[string]internal.Config{"error": &mockConfig{}}} + _, err := NewFactory().CreateMetricsReceiver(context.Background(), creationParams, config, sink) + require.Error(t, err) +} + +func TestGatherMetrics_CreateMetricsScraperError(t *testing.T) { + mFactory := &mockFactory{} + mFactory.On("CreateMetricsScraper").Return(&mockScraper{}, errors.New("err1")) + scraperFactories = map[string]internal.ScraperFactory{mockTypeStr: mFactory} + resourceScraperFactories = map[string]internal.ResourceScraperFactory{} + + sink := new(consumertest.MetricsSink) + config := &Config{Scrapers: map[string]internal.Config{mockTypeStr: &mockConfig{}}} + _, err := NewFactory().CreateMetricsReceiver(context.Background(), creationParams, config, sink) + require.Error(t, err) +} + +func TestGatherMetrics_CreateMetricsResourceScraperError(t *testing.T) { + mResourceFactory := &mockResourceFactory{} + mResourceFactory.On("CreateResourceMetricsScraper").Return(&mockResourceScraper{}, errors.New("err1")) + scraperFactories = map[string]internal.ScraperFactory{} + resourceScraperFactories = map[string]internal.ResourceScraperFactory{mockResourceTypeStr: mResourceFactory} + + sink := new(consumertest.MetricsSink) + config := &Config{Scrapers: map[string]internal.Config{mockResourceTypeStr: &mockConfig{}}} + _, err := NewFactory().CreateMetricsReceiver(context.Background(), creationParams, config, sink) + require.Error(t, err) +} + +type notifyingSink struct { + receivedMetrics bool + timesCalled int + ch chan int +} + +func (s *notifyingSink) ConsumeMetrics(ctx context.Context, md pdata.Metrics) error { + if md.MetricCount() > 0 { + s.receivedMetrics = true + } + + s.timesCalled++ + s.ch <- s.timesCalled + return nil +} + +func benchmarkScrapeMetrics(b *testing.B, cfg *Config) { + scraperFactories = factories + resourceScraperFactories = resourceFactories + + sink := ¬ifyingSink{ch: make(chan int, 10)} + tickerCh := make(chan time.Time) + + options, err := createAddScraperOptions(context.Background(), zap.NewNop(), cfg, scraperFactories, resourceScraperFactories) + require.NoError(b, err) + options = append(options, scraperhelper.WithTickerChannel(tickerCh)) + + receiver, err := scraperhelper.NewScraperControllerReceiver(&cfg.ScraperControllerSettings, zap.NewNop(), sink, options...) + require.NoError(b, err) + + require.NoError(b, receiver.Start(context.Background(), componenttest.NewNopHost())) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + tickerCh <- time.Now() + <-sink.ch + } + + if !sink.receivedMetrics { + b.Fail() + } +} + +func Benchmark_ScrapeCpuMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{cpuscraper.TypeStr: (&cpuscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeDiskMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{diskscraper.TypeStr: (&diskscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeFileSystemMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{filesystemscraper.TypeStr: (&filesystemscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeLoadMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{loadscraper.TypeStr: (&loadscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeMemoryMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{memoryscraper.TypeStr: (&memoryscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeNetworkMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{networkscraper.TypeStr: (&networkscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeProcessesMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{processesscraper.TypeStr: (&processesscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeSwapMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{swapscraper.TypeStr: (&swapscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeProcessMetrics(b *testing.B) { + if runtime.GOOS != "linux" && runtime.GOOS != "windows" { + b.Skip("skipping test on non linux/windows") + } + + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{processscraper.TypeStr: (&processscraper.Factory{}).CreateDefaultConfig()}, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeSystemMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{ + cpuscraper.TypeStr: (&cpuscraper.Factory{}).CreateDefaultConfig(), + diskscraper.TypeStr: (&diskscraper.Factory{}).CreateDefaultConfig(), + filesystemscraper.TypeStr: (&filesystemscraper.Factory{}).CreateDefaultConfig(), + loadscraper.TypeStr: (&loadscraper.Factory{}).CreateDefaultConfig(), + memoryscraper.TypeStr: (&memoryscraper.Factory{}).CreateDefaultConfig(), + networkscraper.TypeStr: (&networkscraper.Factory{}).CreateDefaultConfig(), + processesscraper.TypeStr: (&processesscraper.Factory{}).CreateDefaultConfig(), + swapscraper.TypeStr: (&swapscraper.Factory{}).CreateDefaultConfig(), + }, + } + + benchmarkScrapeMetrics(b, cfg) +} + +func Benchmark_ScrapeSystemAndProcessMetrics(b *testing.B) { + cfg := &Config{ + ScraperControllerSettings: scraperhelper.DefaultScraperControllerSettings(""), + Scrapers: map[string]internal.Config{ + cpuscraper.TypeStr: &cpuscraper.Config{}, + diskscraper.TypeStr: &diskscraper.Config{}, + filesystemscraper.TypeStr: &filesystemscraper.Config{}, + loadscraper.TypeStr: &loadscraper.Config{}, + memoryscraper.TypeStr: &memoryscraper.Config{}, + networkscraper.TypeStr: &networkscraper.Config{}, + processesscraper.TypeStr: &processesscraper.Config{}, + swapscraper.TypeStr: &swapscraper.Config{}, + }, + } + + if runtime.GOOS == "linux" || runtime.GOOS == "windows" { + cfg.Scrapers[processscraper.TypeStr] = &processscraper.Config{} + } + + benchmarkScrapeMetrics(b, cfg) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/metadata/generated_metrics.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/metadata/generated_metrics.go new file mode 100644 index 00000000000..0462614b1a8 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/metadata/generated_metrics.go @@ -0,0 +1,168 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Type is the component type name. +const Type configmodels.Type = "hostmetricsreceiver" + +type metricIntf interface { + Name() string + New() pdata.Metric +} + +// Intentionally not exposing this so that it is opaque and can change freely. +type metricImpl struct { + name string + newFunc func() pdata.Metric +} + +func (m *metricImpl) Name() string { + return m.name +} + +func (m *metricImpl) New() pdata.Metric { + return m.newFunc() +} + +type metricStruct struct { + SystemCPUTime metricIntf + SystemMemoryUsage metricIntf +} + +// Names returns a list of all the metric name strings. +func (m *metricStruct) Names() []string { + return []string{ + "system.cpu.time", + "system.memory.usage", + } +} + +var metricsByName = map[string]metricIntf{ + "system.cpu.time": Metrics.SystemCPUTime, + "system.memory.usage": Metrics.SystemMemoryUsage, +} + +func (m *metricStruct) ByName(n string) metricIntf { + return metricsByName[n] +} + +func (m *metricStruct) FactoriesByName() map[string]func() pdata.Metric { + return map[string]func() pdata.Metric{ + Metrics.SystemCPUTime.Name(): Metrics.SystemCPUTime.New, + Metrics.SystemMemoryUsage.Name(): Metrics.SystemMemoryUsage.New, + } +} + +// Metrics contains a set of methods for each metric that help with +// manipulating those metrics. +var Metrics = &metricStruct{ + &metricImpl{ + "system.cpu.time", + func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.cpu.time") + metric.SetDescription("Total CPU seconds broken down by different states.") + metric.SetUnit("s") + metric.SetDataType(pdata.MetricDataTypeDoubleSum) + metric.DoubleSum().SetIsMonotonic(true) + metric.DoubleSum().SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + + return metric + }, + }, + &metricImpl{ + "system.memory.usage", + func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.memory.usage") + metric.SetDescription("Bytes of memory in use.") + metric.SetUnit("By") + metric.SetDataType(pdata.MetricDataTypeIntSum) + metric.IntSum().SetIsMonotonic(false) + metric.IntSum().SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + + return metric + }, + }, +} + +// M contains a set of methods for each metric that help with +// manipulating those metrics. M is an alias for Metrics +var M = Metrics + +// Labels contains the possible metric labels that can be used. +var Labels = struct { + // Cpu (CPU number starting at 0.) + Cpu string + // CPUState (Breakdown of CPU usage by type.) + CPUState string + // MemState (Breakdown of memory usage by type.) + MemState string +}{ + "cpu", + "state", + "state", +} + +// L contains the possible metric labels that can be used. L is an alias for +// Labels. +var L = Labels + +// LabelCPUState are the possible values that the label "cpu.state" can have. +var LabelCPUState = struct { + Idle string + Interrupt string + Nice string + Softirq string + Steal string + System string + User string + Wait string +}{ + "idle", + "interrupt", + "nice", + "softirq", + "steal", + "system", + "user", + "wait", +} + +// LabelMemState are the possible values that the label "mem.state" can have. +var LabelMemState = struct { + Buffered string + Cached string + Inactive string + Free string + SlabReclaimable string + SlabUnreclaimable string + Used string +}{ + "buffered", + "cached", + "inactive", + "free", + "slab_reclaimable", + "slab_unreclaimable", + "used", +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/doc.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/doc.go new file mode 100644 index 00000000000..6ce1037f033 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/doc.go @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package perfcounters is a thin wrapper around +// https://godoc.org/github.com/leoluk/perflib_exporter/perflib that +// provides functions to scrape raw performance counter data, without +// calculating rates or formatting them, from the registry. +package perfcounters diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper.go new file mode 100644 index 00000000000..bc5e17b9add --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper.go @@ -0,0 +1,202 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package perfcounters + +import ( + "fmt" + "strconv" + "strings" + + "github.com/leoluk/perflib_exporter/perflib" + + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +const totalInstanceName = "_Total" + +// PerfCounterScraper scrapes performance counter data. +type PerfCounterScraper interface { + // start initializes the PerfCounterScraper so that subsequent calls + // to scrape will return performance counter data for the specified set. + // of objects + Initialize(objects ...string) error + // scrape returns performance data for the initialized objects. + Scrape() (PerfDataCollection, error) +} + +// PerfLibScraper is an implementation of PerfCounterScraper that uses +// perflib to scrape performance counter data. +type PerfLibScraper struct { + objectIndices string +} + +func (p *PerfLibScraper) Initialize(objects ...string) error { + // "Counter 009" reads perf counter names in English. + // This is always present regardless of the OS language. + nameTable := perflib.QueryNameTable("Counter 009") + + // lookup object indices from name table + objectIndicesMap := map[uint32]struct{}{} + for _, name := range objects { + index := nameTable.LookupIndex(name) + if index == 0 { + return fmt.Errorf("Failed to retrieve perf counter object %q", name) + } + + objectIndicesMap[index] = struct{}{} + } + + // convert to a space-separated string + objectIndicesSlice := make([]string, 0, len(objectIndicesMap)) + for k := range objectIndicesMap { + objectIndicesSlice = append(objectIndicesSlice, strconv.Itoa(int(k))) + } + p.objectIndices = strings.Join(objectIndicesSlice, " ") + return nil +} + +func (p *PerfLibScraper) Scrape() (PerfDataCollection, error) { + objects, err := perflib.QueryPerformanceData(p.objectIndices) + if err != nil { + return nil, err + } + + indexed := make(map[string]*perflib.PerfObject) + for _, obj := range objects { + indexed[obj.Name] = obj + } + + return perfDataCollection{perfObject: indexed}, nil +} + +// PerfDataCollection represents a collection of perf counter data. +type PerfDataCollection interface { + // GetObject returns the perf counter data associated with the specified object, + // or returns an error if no data exists for this object name. + GetObject(objectName string) (PerfDataObject, error) +} + +type perfDataCollection struct { + perfObject map[string]*perflib.PerfObject +} + +func (p perfDataCollection) GetObject(objectName string) (PerfDataObject, error) { + obj, ok := p.perfObject[objectName] + if !ok { + return nil, fmt.Errorf("Unable to find object %q", objectName) + } + + return perfDataObject{obj}, nil +} + +// PerfDataCollection represents a collection of perf counter values +// and associated instances. +type PerfDataObject interface { + // Filter filters the perf counter data to only retain data related to + // relevant instances based on the supplied parameters. + Filter(includeFS, excludeFS filterset.FilterSet, includeTotal bool) + // GetValues returns the performance counter data associated with the specified + // counters, or returns an error if any of the specified counter names do not + // exist. + GetValues(counterNames ...string) ([]*CounterValues, error) +} + +type perfDataObject struct { + *perflib.PerfObject +} + +func (obj perfDataObject) Filter(includeFS, excludeFS filterset.FilterSet, includeTotal bool) { + if includeFS == nil && excludeFS == nil && includeTotal { + return + } + + filteredDevices := make([]*perflib.PerfInstance, 0, len(obj.Instances)) + for _, device := range obj.Instances { + if includeDevice(device.Name, includeFS, excludeFS, includeTotal) { + filteredDevices = append(filteredDevices, device) + } + } + obj.Instances = filteredDevices +} + +func includeDevice(deviceName string, includeFS, excludeFS filterset.FilterSet, includeTotal bool) bool { + if deviceName == totalInstanceName { + return includeTotal + } + + return (includeFS == nil || includeFS.Matches(deviceName)) && + (excludeFS == nil || !excludeFS.Matches(deviceName)) +} + +// CounterValues represents a set of perf counter values for a given instance. +type CounterValues struct { + InstanceName string + Values map[string]int64 +} + +type counterIndex struct { + index int + name string +} + +func (obj perfDataObject) GetValues(counterNames ...string) ([]*CounterValues, error) { + counterIndices := make([]counterIndex, 0, len(counterNames)) + for idx, counter := range obj.CounterDefs { + // "Base" values give the value of a related counter that pdh.dll uses to compute the derived + // value for this counter. We only care about raw values so ignore base values. See + // https://docs.microsoft.com/en-us/windows/win32/perfctrs/retrieving-counter-data. + if counter.IsBaseValue && !counter.IsNanosecondCounter { + continue + } + + for _, counterName := range counterNames { + if counter.Name == counterName { + counterIndices = append(counterIndices, counterIndex{index: idx, name: counter.Name}) + break + } + } + } + + if len(counterIndices) < len(counterNames) { + return nil, fmt.Errorf("Unable to find counters %q in object %q", missingCounterNames(counterNames, counterIndices), obj.Name) + } + + values := make([]*CounterValues, len(obj.Instances)) + for i, instance := range obj.Instances { + instanceValues := &CounterValues{InstanceName: instance.Name, Values: make(map[string]int64, len(counterIndices))} + for _, counter := range counterIndices { + instanceValues.Values[counter.name] = instance.Counters[counter.index].Value + } + values[i] = instanceValues + } + return values, nil +} + +func missingCounterNames(counterNames []string, counterIndices []counterIndex) []string { + matchedCounters := make(map[string]struct{}, len(counterIndices)) + for _, counter := range counterIndices { + matchedCounters[counter.name] = struct{}{} + } + + counters := make([]string, 0, len(counterNames)-len(matchedCounters)) + for _, counter := range counterNames { + if _, ok := matchedCounters[counter]; !ok { + counters = append(counters, counter) + } + } + return counters +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_mock.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_mock.go new file mode 100644 index 00000000000..861a5cae50a --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_mock.go @@ -0,0 +1,167 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package perfcounters + +import ( + "fmt" + + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +// MockPerfCounterScraperError is an implementation of PerfCounterScraper that returns +// the supplied errors when scrape, GetObject, or GetValues are called. +type MockPerfCounterScraperError struct { + scrapeErr error + getObjectErr error + getValuesErr error +} + +// NewMockPerfCounterScraperError returns a MockPerfCounterScraperError that will return +// the specified errors on subsequent function calls. +func NewMockPerfCounterScraperError(scrapeErr, getObjectErr, getValuesErr error) *MockPerfCounterScraperError { + return &MockPerfCounterScraperError{scrapeErr: scrapeErr, getObjectErr: getObjectErr, getValuesErr: getValuesErr} +} + +// start is a no-op +func (p *MockPerfCounterScraperError) Initialize(objects ...string) error { + return nil +} + +// scrape returns the specified scrapeErr or an object that will return a subsequent error +// if scrapeErr is nil +func (p *MockPerfCounterScraperError) Scrape() (PerfDataCollection, error) { + if p.scrapeErr != nil { + return nil, p.scrapeErr + } + + return mockPerfDataCollectionError{getObjectErr: p.getObjectErr, getValuesErr: p.getValuesErr}, nil +} + +type mockPerfDataCollectionError struct { + getObjectErr error + getValuesErr error +} + +// GetObject returns the specified getObjectErr or an object that will return a subsequent +// error if getObjectErr is nil +func (p mockPerfDataCollectionError) GetObject(objectName string) (PerfDataObject, error) { + if p.getObjectErr != nil { + return nil, p.getObjectErr + } + + return mockPerfDataObjectError{getValuesErr: p.getValuesErr}, nil +} + +type mockPerfDataObjectError struct { + getValuesErr error +} + +// Filter is a no-op +func (obj mockPerfDataObjectError) Filter(includeFS, excludeFS filterset.FilterSet, includeTotal bool) { +} + +// GetValues returns the specified getValuesErr +func (obj mockPerfDataObjectError) GetValues(counterNames ...string) ([]*CounterValues, error) { + return nil, obj.getValuesErr +} + +// MockPerfCounterScraper is an implementation of PerfCounterScraper that returns the supplied +// object / counter values on each successive call to scrape, in the specified order. +// +// Example Usage: +// +// s := NewMockPerfCounterScraper(map[string]map[string][]int64{ +// "Object1": map[string][]int64{ +// "Counter1": []int64{1, 2}, +// "Counter2": []int64{4}, +// }, +// }) +// +// s.scrape().GetObject("Object1").GetValues("Counter1", "Counter2") +// +// ... 1st call returns []*CounterValues{ { Values: { "Counter1": 1, "Counter2": 4 } } } +// ... 2nd call returns []*CounterValues{ { Values: { "Counter1": 2, "Counter2": 4 } } } +type MockPerfCounterScraper struct { + objectsAndValuesToReturn map[string]map[string][]int64 + timesCalled int +} + +// NewMockPerfCounterScraper returns a MockPerfCounterScraper that will return the supplied +// object / counter values on each successive call to scrape, in the specified order. +func NewMockPerfCounterScraper(objectsAndValuesToReturn map[string]map[string][]int64) *MockPerfCounterScraper { + return &MockPerfCounterScraper{objectsAndValuesToReturn: objectsAndValuesToReturn} +} + +// start is a no-op +func (p *MockPerfCounterScraper) Initialize(objects ...string) error { + return nil +} + +// scrape returns a perf data collection with the supplied object / counter values, +// according to the supplied order. +func (p *MockPerfCounterScraper) Scrape() (PerfDataCollection, error) { + objectsAndValuesToReturn := make(map[string]map[string]int64, len(p.objectsAndValuesToReturn)) + for objectName, countersToReturn := range p.objectsAndValuesToReturn { + valuesToReturn := make(map[string]int64, len(countersToReturn)) + for counterName, orderedValuesToReturn := range countersToReturn { + returnIndex := p.timesCalled + if returnIndex >= len(orderedValuesToReturn) { + returnIndex = len(orderedValuesToReturn) - 1 + } + valuesToReturn[counterName] = orderedValuesToReturn[returnIndex] + } + objectsAndValuesToReturn[objectName] = valuesToReturn + } + + p.timesCalled++ + return mockPerfDataCollection{objectsAndValuesToReturn: objectsAndValuesToReturn}, nil +} + +type mockPerfDataCollection struct { + objectsAndValuesToReturn map[string]map[string]int64 +} + +// GetObject returns the specified object / counter values +func (p mockPerfDataCollection) GetObject(objectName string) (PerfDataObject, error) { + valuesToReturn, ok := p.objectsAndValuesToReturn[objectName] + if !ok { + return nil, fmt.Errorf("Unable to find object %q", objectName) + } + + return mockPerfDataObject{valuesToReturn: valuesToReturn}, nil +} + +type mockPerfDataObject struct { + valuesToReturn map[string]int64 +} + +// Filter is a no-op +func (obj mockPerfDataObject) Filter(includeFS, excludeFS filterset.FilterSet, includeTotal bool) { +} + +// GetValues returns the specified counter values +func (obj mockPerfDataObject) GetValues(counterNames ...string) ([]*CounterValues, error) { + value := &CounterValues{Values: map[string]int64{}} + for _, counterName := range counterNames { + valueToReturn, ok := obj.valuesToReturn[counterName] + if !ok { + return nil, fmt.Errorf("Mock Perf Counter Scraper configured incorrectly. Return value for counter %q not specified", counterName) + } + value.Values[counterName] = valueToReturn + } + return []*CounterValues{value}, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_test.go new file mode 100644 index 00000000000..c699f0e99cc --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/perfcounters/perfcounter_scraper_test.go @@ -0,0 +1,171 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package perfcounters + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/internal/processor/filterset" +) + +func Test_PerfCounterScraper(t *testing.T) { + type testCase struct { + name string + // NewPerfCounter + objects []string + newErr string + expectIndices []string + // Filter + includeFS filterset.FilterSet + excludeFS filterset.FilterSet + includeTotal bool + // GetObject + getObject string + getObjectErr string + // GetCounterValues + getCounters []string + getCountersErr string + expectedInstanceNames []string + excludedInstanceNames []string + expectedMinimumInstances int + } + + excludedCommonDrives := []string{"C:"} + excludeCommonDriveFilterSet, err := filterset.CreateFilterSet(excludedCommonDrives, &filterset.Config{MatchType: filterset.Strict}) + require.NoError(t, err) + + testCases := []testCase{ + { + name: "Standard", + objects: []string{"Memory"}, + expectIndices: []string{"4"}, + getObject: "Memory", + getCounters: []string{"Committed Bytes"}, + expectedInstanceNames: []string{""}, + }, + { + name: "Multiple Objects & Values", + objects: []string{"Memory", "LogicalDisk"}, + expectIndices: []string{"4", "236"}, + getObject: "LogicalDisk", + getCounters: []string{"Disk Reads/sec", "Disk Writes/sec"}, + expectedMinimumInstances: 1, + }, + { + name: "Filtered", + objects: []string{"LogicalDisk"}, + expectIndices: []string{"236"}, + excludeFS: excludeCommonDriveFilterSet, + includeTotal: true, + getObject: "LogicalDisk", + getCounters: []string{"Disk Reads/sec"}, + excludedInstanceNames: excludedCommonDrives, + }, + { + name: "New Error", + objects: []string{"Memory", "Invalid Object 1", "Invalid Object 2"}, + newErr: `Failed to retrieve perf counter object "Invalid Object 1"`, + }, + { + name: "Get Object Error", + objects: []string{"Memory"}, + expectIndices: []string{"4"}, + getObject: "Invalid Object 1", + getObjectErr: `Unable to find object "Invalid Object 1"`, + }, + { + name: "Get Values Error", + objects: []string{"Memory"}, + expectIndices: []string{"4"}, + getObject: "Memory", + getCounters: []string{"Committed Bytes", "Invalid Counter 1", "Invalid Counter 2"}, + getCountersErr: `Unable to find counters ["Invalid Counter 1" "Invalid Counter 2"] in object "Memory"`, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + s := &PerfLibScraper{} + err := s.Initialize(test.objects...) + if test.newErr != "" { + assert.EqualError(t, err, test.newErr) + return + } + require.NoError(t, err, "Failed to create new perf counter scraper: %v", err) + + assert.ElementsMatch(t, test.expectIndices, strings.Split(s.objectIndices, " ")) + + c, err := s.Scrape() + require.NoError(t, err, "Failed to scrape data: %v", err) + + p, err := c.GetObject(test.getObject) + if test.getObjectErr != "" { + assert.EqualError(t, err, test.getObjectErr) + return + } + require.NoError(t, err, "Failed to get object: %v", err) + + p.Filter(test.includeFS, test.excludeFS, test.includeTotal) + + counterValues, err := p.GetValues(test.getCounters...) + if test.getCountersErr != "" { + assert.EqualError(t, err, test.getCountersErr) + return + } + require.NoError(t, err, "Failed to get counter: %v", err) + + assert.GreaterOrEqual(t, len(counterValues), test.expectedMinimumInstances) + + if len(test.expectedInstanceNames) > 0 { + for _, expectedName := range test.expectedInstanceNames { + var gotName bool + for _, cv := range counterValues { + if cv.InstanceName == expectedName { + gotName = true + break + } + } + assert.Truef(t, gotName, "Expected Instance %q was not returned", expectedName) + } + } + + if len(test.excludedInstanceNames) > 0 { + for _, excludedName := range test.excludedInstanceNames { + for _, cv := range counterValues { + if cv.InstanceName == excludedName { + assert.Fail(t, "", "Excluded Instance %q was returned", excludedName) + break + } + } + } + } + + var includesTotal bool + for _, cv := range counterValues { + if cv.InstanceName == "_Total" { + includesTotal = true + break + } + } + assert.Equalf(t, test.includeTotal, includesTotal, "_Total was returned: %v (expected the opposite)", test.includeTotal, includesTotal) + }) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper.go new file mode 100644 index 00000000000..45de375706a --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// BaseFactory for creating Scrapers. +type BaseFactory interface { + // CreateDefaultConfig creates the default configuration for the Scraper. + CreateDefaultConfig() Config +} + +// ScraperFactory can create a MetricScraper. +type ScraperFactory interface { + BaseFactory + + // CreateMetricsScraper creates a scraper based on this config. + // If the config is not valid, error will be returned instead. + CreateMetricsScraper(ctx context.Context, logger *zap.Logger, cfg Config) (scraperhelper.MetricsScraper, error) +} + +// ResourceScraperFactory can create a ResourceScraper. +type ResourceScraperFactory interface { + BaseFactory + + // CreateResourceMetricsScraper creates a resource scraper based on this + // config. If the config is not valid, error will be returned instead. + CreateResourceMetricsScraper(ctx context.Context, logger *zap.Logger, cfg Config) (scraperhelper.ResourceMetricsScraper, error) +} + +// Config is the configuration of a scraper. +type Config interface { +} + +// ConfigSettings provides common settings for scraper configuration. +type ConfigSettings struct { +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go new file mode 100644 index 00000000000..0cf488103d9 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cpuscraper + +import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + +// Config relating to CPU Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper.go new file mode 100644 index 00000000000..1884b65a648 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper.go @@ -0,0 +1,95 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cpuscraper + +import ( + "context" + "time" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/host" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const metricsLen = 1 + +// scraper for CPU Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + + // for mocking + bootTime func() (uint64, error) + times func(bool) ([]cpu.TimesStat, error) +} + +// newCPUScraper creates a set of CPU related metrics +func newCPUScraper(_ context.Context, cfg *Config) *scraper { + return &scraper{config: cfg, bootTime: host.BootTime, times: cpu.Times} +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + return nil +} + +func (s *scraper) scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + now := internal.TimeToUnixNano(time.Now()) + cpuTimes, err := s.times( /*percpu=*/ true) + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + metrics.Resize(metricsLen) + initializeCPUTimeMetric(metrics.At(0), s.startTime, now, cpuTimes) + return metrics, nil +} + +func initializeCPUTimeMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, cpuTimes []cpu.TimesStat) { + metadata.Metrics.SystemCPUTime.New().CopyTo(metric) + + ddps := metric.DoubleSum().DataPoints() + ddps.Resize(len(cpuTimes) * cpuStatesLen) + for i, cpuTime := range cpuTimes { + appendCPUTimeStateDataPoints(ddps, i*cpuStatesLen, startTime, now, cpuTime) + } +} + +const gopsCPUTotal string = "cpu-total" + +func initializeCPUTimeDataPoint(dataPoint pdata.DoubleDataPoint, startTime, now pdata.TimestampUnixNano, cpuLabel string, stateLabel string, value float64) { + labelsMap := dataPoint.LabelsMap() + // ignore cpu label if reporting "total" cpu usage + if cpuLabel != gopsCPUTotal { + labelsMap.Insert(metadata.Labels.Cpu, cpuLabel) + } + labelsMap.Insert(metadata.Labels.CPUState, stateLabel) + + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_linux.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_linux.go new file mode 100644 index 00000000000..627c66c315f --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_linux.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package cpuscraper + +import ( + "github.com/shirou/gopsutil/cpu" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const cpuStatesLen = 8 + +func appendCPUTimeStateDataPoints(ddps pdata.DoubleDataPointSlice, startIdx int, startTime, now pdata.TimestampUnixNano, cpuTime cpu.TimesStat) { + initializeCPUTimeDataPoint(ddps.At(startIdx+0), startTime, now, cpuTime.CPU, metadata.LabelCPUState.User, cpuTime.User) + initializeCPUTimeDataPoint(ddps.At(startIdx+1), startTime, now, cpuTime.CPU, metadata.LabelCPUState.System, cpuTime.System) + initializeCPUTimeDataPoint(ddps.At(startIdx+2), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Idle, cpuTime.Idle) + initializeCPUTimeDataPoint(ddps.At(startIdx+3), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Interrupt, cpuTime.Irq) + initializeCPUTimeDataPoint(ddps.At(startIdx+4), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Nice, cpuTime.Nice) + initializeCPUTimeDataPoint(ddps.At(startIdx+5), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Softirq, cpuTime.Softirq) + initializeCPUTimeDataPoint(ddps.At(startIdx+6), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Steal, cpuTime.Steal) + initializeCPUTimeDataPoint(ddps.At(startIdx+7), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Wait, cpuTime.Iowait) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_others.go new file mode 100644 index 00000000000..5257d5b5e27 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_others.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux + +package cpuscraper + +import ( + "github.com/shirou/gopsutil/cpu" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const cpuStatesLen = 4 + +func appendCPUTimeStateDataPoints(ddps pdata.DoubleDataPointSlice, startIdx int, startTime, now pdata.TimestampUnixNano, cpuTime cpu.TimesStat) { + initializeCPUTimeDataPoint(ddps.At(startIdx+0), startTime, now, cpuTime.CPU, metadata.LabelCPUState.User, cpuTime.User) + initializeCPUTimeDataPoint(ddps.At(startIdx+1), startTime, now, cpuTime.CPU, metadata.LabelCPUState.System, cpuTime.System) + initializeCPUTimeDataPoint(ddps.At(startIdx+2), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Idle, cpuTime.Idle) + initializeCPUTimeDataPoint(ddps.At(startIdx+3), startTime, now, cpuTime.CPU, metadata.LabelCPUState.Interrupt, cpuTime.Irq) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_test.go new file mode 100644 index 00000000000..cde0d8ece68 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper_test.go @@ -0,0 +1,127 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cpuscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/shirou/gopsutil/cpu" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + bootTimeFunc func() (uint64, error) + timesFunc func(bool) ([]cpu.TimesStat, error) + expectedStartTime pdata.TimestampUnixNano + initializationErr string + expectedErr string + } + + testCases := []testCase{ + { + name: "Standard", + }, + { + name: "Validate Start Time", + bootTimeFunc: func() (uint64, error) { return 100, nil }, + expectedStartTime: 100 * 1e9, + }, + { + name: "Boot Time Error", + bootTimeFunc: func() (uint64, error) { return 0, errors.New("err1") }, + initializationErr: "err1", + }, + { + name: "Times Error", + timesFunc: func(bool) ([]cpu.TimesStat, error) { return nil, errors.New("err2") }, + expectedErr: "err2", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newCPUScraper(context.Background(), &Config{}) + if test.bootTimeFunc != nil { + scraper.bootTime = test.bootTimeFunc + } + if test.timesFunc != nil { + scraper.times = test.timesFunc + } + + err := scraper.start(context.Background(), componenttest.NewNopHost()) + if test.initializationErr != "" { + assert.EqualError(t, err, test.initializationErr) + return + } + require.NoError(t, err, "Failed to initialize cpu scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, 1, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + assert.Equal(t, 1, metrics.Len()) + + assertCPUMetricValid(t, metrics.At(0), metadata.Metrics.SystemCPUTime.New(), test.expectedStartTime) + + if runtime.GOOS == "linux" { + assertCPUMetricHasLinuxSpecificStateLabels(t, metrics.At(0)) + } + + internal.AssertSameTimeStampForAllMetrics(t, metrics) + }) + } +} + +func assertCPUMetricValid(t *testing.T, metric pdata.Metric, descriptor pdata.Metric, startTime pdata.TimestampUnixNano) { + internal.AssertDescriptorEqual(t, descriptor, metric) + if startTime != 0 { + internal.AssertDoubleSumMetricStartTimeEquals(t, metric, startTime) + } + assert.GreaterOrEqual(t, metric.DoubleSum().DataPoints().Len(), 4*runtime.NumCPU()) + internal.AssertDoubleSumMetricLabelExists(t, metric, 0, metadata.Labels.Cpu) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 0, metadata.Labels.CPUState, metadata.LabelCPUState.User) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 1, metadata.Labels.CPUState, metadata.LabelCPUState.System) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 2, metadata.Labels.CPUState, metadata.LabelCPUState.Idle) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 3, metadata.Labels.CPUState, metadata.LabelCPUState.Interrupt) +} + +func assertCPUMetricHasLinuxSpecificStateLabels(t *testing.T, metric pdata.Metric) { + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 4, metadata.Labels.CPUState, metadata.LabelCPUState.Nice) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 5, metadata.Labels.CPUState, metadata.LabelCPUState.Softirq) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 6, metadata.Labels.CPUState, metadata.LabelCPUState.Steal) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 7, metadata.Labels.CPUState, metadata.LabelCPUState.Wait) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory.go new file mode 100644 index 00000000000..8411395ddb0 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cpuscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for CPU scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "cpu" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s := newCPUScraper(ctx, cfg) + + ms := scraperhelper.NewMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory_test.go new file mode 100644 index 00000000000..07dd6afd07b --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/factory_test.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cpuscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go new file mode 100644 index 00000000000..e36646f3e8d --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskscraper + +import ( + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +// Config relating to Disk Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // Include specifies a filter on the devices that should be included from the generated metrics. + // Exclude specifies a filter on the devices that should be excluded from the generated metrics. + // If neither `include` or `exclude` are set, metrics will be generated for all devices. + Include MatchConfig `mapstructure:"include"` + Exclude MatchConfig `mapstructure:"exclude"` +} + +type MatchConfig struct { + filterset.Config `mapstructure:",squash"` + + Devices []string `mapstructure:"devices"` +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_metadata.go new file mode 100644 index 00000000000..8ffd1129135 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_metadata.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// labels + +const ( + deviceLabelName = "device" + directionLabelName = "direction" +) + +// direction label values + +const ( + readDirectionLabelValue = "read" + writeDirectionLabelValue = "write" +) + +// descriptors + +var diskIODescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.disk.io") + metric.SetDescription("Disk bytes transferred.") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var diskOpsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.disk.ops") + metric.SetDescription("Disk operations count.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var diskIOTimeDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.disk.io_time") + metric.SetDescription("Time disk spent activated. On Windows, this is calculated as the inverse of disk idle time.") + metric.SetUnit("s") + metric.SetDataType(pdata.MetricDataTypeDoubleSum) + sum := metric.DoubleSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var diskOperationTimeDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.disk.operation_time") + metric.SetDescription("Time spent in disk operations.") + metric.SetUnit("s") + metric.SetDataType(pdata.MetricDataTypeDoubleSum) + sum := metric.DoubleSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var diskPendingOperationsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.disk.pending_operations") + metric.SetDescription("The queue size of pending I/O operations.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var diskMergedDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.disk.merged") + metric.SetDescription("The number of disk reads merged into single physical disk access operations.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others.go new file mode 100644 index 00000000000..7d2a696507a --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others.go @@ -0,0 +1,222 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package diskscraper + +import ( + "context" + "fmt" + "time" + + "github.com/shirou/gopsutil/disk" + "github.com/shirou/gopsutil/host" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +const ( + standardMetricsLen = 5 + metricsLen = standardMetricsLen + systemSpecificMetricsLen +) + +// scraper for Disk Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + includeFS filterset.FilterSet + excludeFS filterset.FilterSet + + // for mocking + bootTime func() (uint64, error) + ioCounters func(names ...string) (map[string]disk.IOCountersStat, error) +} + +// newDiskScraper creates a Disk Scraper +func newDiskScraper(_ context.Context, cfg *Config) (*scraper, error) { + scraper := &scraper{config: cfg, bootTime: host.BootTime, ioCounters: disk.IOCounters} + + var err error + + if len(cfg.Include.Devices) > 0 { + scraper.includeFS, err = filterset.CreateFilterSet(cfg.Include.Devices, &cfg.Include.Config) + if err != nil { + return nil, fmt.Errorf("error creating device include filters: %w", err) + } + } + + if len(cfg.Exclude.Devices) > 0 { + scraper.excludeFS, err = filterset.CreateFilterSet(cfg.Exclude.Devices, &cfg.Exclude.Config) + if err != nil { + return nil, fmt.Errorf("error creating device exclude filters: %w", err) + } + } + + return scraper, nil +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + return nil +} + +func (s *scraper) scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + now := internal.TimeToUnixNano(time.Now()) + ioCounters, err := s.ioCounters() + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + // filter devices by name + ioCounters = s.filterByDevice(ioCounters) + + if len(ioCounters) > 0 { + metrics.Resize(metricsLen) + initializeDiskIOMetric(metrics.At(0), s.startTime, now, ioCounters) + initializeDiskOpsMetric(metrics.At(1), s.startTime, now, ioCounters) + initializeDiskIOTimeMetric(metrics.At(2), s.startTime, now, ioCounters) + initializeDiskOperationTimeMetric(metrics.At(3), s.startTime, now, ioCounters) + initializeDiskPendingOperationsMetric(metrics.At(4), now, ioCounters) + appendSystemSpecificMetrics(metrics, 5, s.startTime, now, ioCounters) + } + + return metrics, nil +} + +func initializeDiskIOMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { + diskIODescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCounters)) + + idx := 0 + for device, ioCounter := range ioCounters { + initializeInt64DataPoint(idps.At(idx+0), startTime, now, device, readDirectionLabelValue, int64(ioCounter.ReadBytes)) + initializeInt64DataPoint(idps.At(idx+1), startTime, now, device, writeDirectionLabelValue, int64(ioCounter.WriteBytes)) + idx += 2 + } +} + +func initializeDiskOpsMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { + diskOpsDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCounters)) + + idx := 0 + for device, ioCounter := range ioCounters { + initializeInt64DataPoint(idps.At(idx+0), startTime, now, device, readDirectionLabelValue, int64(ioCounter.ReadCount)) + initializeInt64DataPoint(idps.At(idx+1), startTime, now, device, writeDirectionLabelValue, int64(ioCounter.WriteCount)) + idx += 2 + } +} + +func initializeDiskIOTimeMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { + diskIOTimeDescriptor.CopyTo(metric) + + ddps := metric.DoubleSum().DataPoints() + ddps.Resize(len(ioCounters)) + + idx := 0 + for device, ioCounter := range ioCounters { + initializeDoubleDataPoint(ddps.At(idx+0), startTime, now, device, "", float64(ioCounter.IoTime)/1e3) + idx++ + } +} + +func initializeDiskOperationTimeMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { + diskOperationTimeDescriptor.CopyTo(metric) + + ddps := metric.DoubleSum().DataPoints() + ddps.Resize(2 * len(ioCounters)) + + idx := 0 + for device, ioCounter := range ioCounters { + initializeDoubleDataPoint(ddps.At(idx+0), startTime, now, device, readDirectionLabelValue, float64(ioCounter.ReadTime)/1e3) + initializeDoubleDataPoint(ddps.At(idx+1), startTime, now, device, writeDirectionLabelValue, float64(ioCounter.WriteTime)/1e3) + idx += 2 + } +} + +func initializeDiskPendingOperationsMetric(metric pdata.Metric, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { + diskPendingOperationsDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(len(ioCounters)) + + idx := 0 + for device, ioCounter := range ioCounters { + initializeDiskPendingDataPoint(idps.At(idx), now, device, int64(ioCounter.IopsInProgress)) + idx++ + } +} + +func initializeInt64DataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, deviceLabel string, directionLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + if directionLabel != "" { + labelsMap.Insert(directionLabelName, directionLabel) + } + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func initializeDoubleDataPoint(dataPoint pdata.DoubleDataPoint, startTime, now pdata.TimestampUnixNano, deviceLabel string, directionLabel string, value float64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + if directionLabel != "" { + labelsMap.Insert(directionLabelName, directionLabel) + } + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func initializeDiskPendingDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, deviceLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func (s *scraper) filterByDevice(ioCounters map[string]disk.IOCountersStat) map[string]disk.IOCountersStat { + if s.includeFS == nil && s.excludeFS == nil { + return ioCounters + } + + for device := range ioCounters { + if !s.includeDevice(device) { + delete(ioCounters, device) + } + } + return ioCounters +} + +func (s *scraper) includeDevice(deviceName string) bool { + return (s.includeFS == nil || s.includeFS.Matches(deviceName)) && + (s.excludeFS == nil || !s.excludeFS.Matches(deviceName)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_fallback.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_fallback.go new file mode 100644 index 00000000000..5c169a874b4 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_fallback.go @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux,!windows + +package diskscraper + +import ( + "github.com/shirou/gopsutil/disk" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const systemSpecificMetricsLen = 0 + +func appendSystemSpecificMetrics(metrics pdata.MetricSlice, startIdx int, startTime, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_linux.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_linux.go new file mode 100644 index 00000000000..541a28bfdd9 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_linux.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package diskscraper + +import ( + "github.com/shirou/gopsutil/disk" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const systemSpecificMetricsLen = 1 + +func appendSystemSpecificMetrics(metrics pdata.MetricSlice, startIdx int, startTime, now pdata.TimestampUnixNano, ioCounters map[string]disk.IOCountersStat) { + metric := metrics.At(startIdx) + diskMergedDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCounters)) + + idx := 0 + for device, ioCounter := range ioCounters { + initializeInt64DataPoint(idps.At(idx+0), startTime, now, device, readDirectionLabelValue, int64(ioCounter.MergedReadCount)) + initializeInt64DataPoint(idps.At(idx+1), startTime, now, device, writeDirectionLabelValue, int64(ioCounter.MergedWriteCount)) + idx += 2 + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_test.go new file mode 100644 index 00000000000..516e233e634 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_others_test.go @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package diskscraper + +import ( + "context" + "errors" + "testing" + + "github.com/shirou/gopsutil/disk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" +) + +func TestScrape_Others(t *testing.T) { + type testCase struct { + name string + ioCountersFunc func(names ...string) (map[string]disk.IOCountersStat, error) + expectedErr string + } + + testCases := []testCase{ + { + name: "Error", + ioCountersFunc: func(names ...string) (map[string]disk.IOCountersStat, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper, err := newDiskScraper(context.Background(), &Config{}) + require.NoError(t, err, "Failed to create disk scraper: %v", err) + + if test.ioCountersFunc != nil { + scraper.ioCounters = test.ioCountersFunc + } + + err = scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize disk scraper: %v", err) + + _, err = scraper.scrape(context.Background()) + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, metricsLen, err.(consumererror.PartialScrapeError).Failed) + } + }) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_test.go new file mode 100644 index 00000000000..921120dd7b0 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_test.go @@ -0,0 +1,164 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + config Config + bootTimeFunc func() (uint64, error) + newErrRegex string + initializationErr string + expectMetrics bool + expectedStartTime pdata.TimestampUnixNano + } + + testCases := []testCase{ + { + name: "Standard", + expectMetrics: true, + }, + { + name: "Validate Start Time", + bootTimeFunc: func() (uint64, error) { return 100, nil }, + expectMetrics: true, + expectedStartTime: 100 * 1e9, + }, + { + name: "Boot Time Error", + bootTimeFunc: func() (uint64, error) { return 0, errors.New("err1") }, + initializationErr: "err1", + }, + { + name: "Include Filter that matches nothing", + config: Config{Include: MatchConfig{filterset.Config{MatchType: "strict"}, []string{"@*^#&*$^#)"}}}, + expectMetrics: false, + }, + { + name: "Invalid Include Filter", + config: Config{Include: MatchConfig{Devices: []string{"test"}}}, + newErrRegex: "^error creating device include filters:", + }, + { + name: "Invalid Exclude Filter", + config: Config{Exclude: MatchConfig{Devices: []string{"test"}}}, + newErrRegex: "^error creating device exclude filters:", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper, err := newDiskScraper(context.Background(), &test.config) + if test.newErrRegex != "" { + require.Error(t, err) + require.Regexp(t, test.newErrRegex, err) + return + } + require.NoError(t, err, "Failed to create disk scraper: %v", err) + + if test.bootTimeFunc != nil { + scraper.bootTime = test.bootTimeFunc + } + + err = scraper.start(context.Background(), componenttest.NewNopHost()) + if test.initializationErr != "" { + assert.EqualError(t, err, test.initializationErr) + return + } + require.NoError(t, err, "Failed to initialize disk scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + if !test.expectMetrics { + assert.Equal(t, 0, metrics.Len()) + return + } + + assert.GreaterOrEqual(t, metrics.Len(), 4) + + assertInt64DiskMetricValid(t, metrics.At(0), diskIODescriptor, true, test.expectedStartTime) + assertInt64DiskMetricValid(t, metrics.At(1), diskOpsDescriptor, true, test.expectedStartTime) + assertDoubleDiskMetricValid(t, metrics.At(2), diskIOTimeDescriptor, false, test.expectedStartTime) + assertDoubleDiskMetricValid(t, metrics.At(3), diskOperationTimeDescriptor, true, test.expectedStartTime) + assertDiskPendingOperationsMetricValid(t, metrics.At(4)) + + if runtime.GOOS == "linux" { + assertInt64DiskMetricValid(t, metrics.At(5), diskMergedDescriptor, true, test.expectedStartTime) + } + + internal.AssertSameTimeStampForAllMetrics(t, metrics) + }) + } +} + +func assertInt64DiskMetricValid(t *testing.T, metric pdata.Metric, expectedDescriptor pdata.Metric, expectDirectionLabels bool, startTime pdata.TimestampUnixNano) { + internal.AssertDescriptorEqual(t, expectedDescriptor, metric) + if startTime != 0 { + internal.AssertIntSumMetricStartTimeEquals(t, metric, startTime) + } + + minExpectedPoints := 1 + if expectDirectionLabels { + minExpectedPoints = 2 + } + assert.GreaterOrEqual(t, metric.IntSum().DataPoints().Len(), minExpectedPoints) + + internal.AssertIntSumMetricLabelExists(t, metric, 0, deviceLabelName) + if expectDirectionLabels { + internal.AssertIntSumMetricLabelHasValue(t, metric, 0, directionLabelName, readDirectionLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, metric, 1, directionLabelName, writeDirectionLabelValue) + } +} + +func assertDoubleDiskMetricValid(t *testing.T, metric pdata.Metric, expectedDescriptor pdata.Metric, expectDirectionLabels bool, startTime pdata.TimestampUnixNano) { + internal.AssertDescriptorEqual(t, expectedDescriptor, metric) + if startTime != 0 { + internal.AssertDoubleSumMetricStartTimeEquals(t, metric, startTime) + } + + minExpectedPoints := 1 + if expectDirectionLabels { + minExpectedPoints = 2 + } + assert.GreaterOrEqual(t, metric.DoubleSum().DataPoints().Len(), minExpectedPoints) + + internal.AssertDoubleSumMetricLabelExists(t, metric, 0, deviceLabelName) + if expectDirectionLabels { + internal.AssertDoubleSumMetricLabelHasValue(t, metric, 0, directionLabelName, readDirectionLabelValue) + internal.AssertDoubleSumMetricLabelHasValue(t, metric, metric.DoubleSum().DataPoints().Len()-1, directionLabelName, writeDirectionLabelValue) + } +} + +func assertDiskPendingOperationsMetricValid(t *testing.T, metric pdata.Metric) { + internal.AssertDescriptorEqual(t, diskPendingOperationsDescriptor, metric) + assert.GreaterOrEqual(t, metric.IntSum().DataPoints().Len(), 1) + internal.AssertIntSumMetricLabelExists(t, metric, 0, deviceLabelName) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows.go new file mode 100644 index 00000000000..0a6a56afd3c --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows.go @@ -0,0 +1,214 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskscraper + +import ( + "context" + "fmt" + "time" + + "github.com/shirou/gopsutil/host" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/perfcounters" +) + +const ( + metricsLen = 5 + + logicalDisk = "LogicalDisk" + + readsPerSec = "Disk Reads/sec" + writesPerSec = "Disk Writes/sec" + + readBytesPerSec = "Disk Read Bytes/sec" + writeBytesPerSec = "Disk Write Bytes/sec" + + idleTime = "% Idle Time" + + avgDiskSecsPerRead = "Avg. Disk sec/Read" + avgDiskSecsPerWrite = "Avg. Disk sec/Write" + + queueLength = "Current Disk Queue Length" +) + +// scraper for Disk Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + includeFS filterset.FilterSet + excludeFS filterset.FilterSet + + perfCounterScraper perfcounters.PerfCounterScraper + + // for mocking + bootTime func() (uint64, error) +} + +// newDiskScraper creates a Disk Scraper +func newDiskScraper(_ context.Context, cfg *Config) (*scraper, error) { + scraper := &scraper{config: cfg, perfCounterScraper: &perfcounters.PerfLibScraper{}, bootTime: host.BootTime} + + var err error + + if len(cfg.Include.Devices) > 0 { + scraper.includeFS, err = filterset.CreateFilterSet(cfg.Include.Devices, &cfg.Include.Config) + if err != nil { + return nil, fmt.Errorf("error creating device include filters: %w", err) + } + } + + if len(cfg.Exclude.Devices) > 0 { + scraper.excludeFS, err = filterset.CreateFilterSet(cfg.Exclude.Devices, &cfg.Exclude.Config) + if err != nil { + return nil, fmt.Errorf("error creating device exclude filters: %w", err) + } + } + + return scraper, nil +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + + return s.perfCounterScraper.Initialize(logicalDisk) +} + +func (s *scraper) scrape(ctx context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + now := internal.TimeToUnixNano(time.Now()) + + counters, err := s.perfCounterScraper.Scrape() + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + logicalDiskObject, err := counters.GetObject(logicalDisk) + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + // filter devices by name + logicalDiskObject.Filter(s.includeFS, s.excludeFS, false) + + logicalDiskCounterValues, err := logicalDiskObject.GetValues(readsPerSec, writesPerSec, readBytesPerSec, writeBytesPerSec, idleTime, avgDiskSecsPerRead, avgDiskSecsPerWrite, queueLength) + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + if len(logicalDiskCounterValues) > 0 { + metrics.Resize(metricsLen) + initializeDiskIOMetric(metrics.At(0), s.startTime, now, logicalDiskCounterValues) + initializeDiskOpsMetric(metrics.At(1), s.startTime, now, logicalDiskCounterValues) + initializeDiskIOTimeMetric(metrics.At(2), s.startTime, now, logicalDiskCounterValues) + initializeDiskOperationTimeMetric(metrics.At(3), s.startTime, now, logicalDiskCounterValues) + initializeDiskPendingOperationsMetric(metrics.At(4), now, logicalDiskCounterValues) + } + + return metrics, nil +} + +func initializeDiskIOMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, logicalDiskCounterValues []*perfcounters.CounterValues) { + diskIODescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(logicalDiskCounterValues)) + for idx, logicalDiskCounter := range logicalDiskCounterValues { + initializeInt64DataPoint(idps.At(2*idx+0), startTime, now, logicalDiskCounter.InstanceName, readDirectionLabelValue, logicalDiskCounter.Values[readBytesPerSec]) + initializeInt64DataPoint(idps.At(2*idx+1), startTime, now, logicalDiskCounter.InstanceName, writeDirectionLabelValue, logicalDiskCounter.Values[writeBytesPerSec]) + } +} + +func initializeDiskOpsMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, logicalDiskCounterValues []*perfcounters.CounterValues) { + diskOpsDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(logicalDiskCounterValues)) + for idx, logicalDiskCounter := range logicalDiskCounterValues { + initializeInt64DataPoint(idps.At(2*idx+0), startTime, now, logicalDiskCounter.InstanceName, readDirectionLabelValue, logicalDiskCounter.Values[readsPerSec]) + initializeInt64DataPoint(idps.At(2*idx+1), startTime, now, logicalDiskCounter.InstanceName, writeDirectionLabelValue, logicalDiskCounter.Values[writesPerSec]) + } +} + +func initializeDiskIOTimeMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, logicalDiskCounterValues []*perfcounters.CounterValues) { + diskIOTimeDescriptor.CopyTo(metric) + + ddps := metric.DoubleSum().DataPoints() + ddps.Resize(len(logicalDiskCounterValues)) + for idx, logicalDiskCounter := range logicalDiskCounterValues { + // disk active time = system boot time - disk idle time + initializeDoubleDataPoint(ddps.At(idx), startTime, now, logicalDiskCounter.InstanceName, "", float64(now-startTime)/1e9-float64(logicalDiskCounter.Values[idleTime])/1e7) + } +} + +func initializeDiskOperationTimeMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, logicalDiskCounterValues []*perfcounters.CounterValues) { + diskOperationTimeDescriptor.CopyTo(metric) + + ddps := metric.DoubleSum().DataPoints() + ddps.Resize(2 * len(logicalDiskCounterValues)) + for idx, logicalDiskCounter := range logicalDiskCounterValues { + initializeDoubleDataPoint(ddps.At(2*idx+0), startTime, now, logicalDiskCounter.InstanceName, readDirectionLabelValue, float64(logicalDiskCounter.Values[avgDiskSecsPerRead])/1e7) + initializeDoubleDataPoint(ddps.At(2*idx+1), startTime, now, logicalDiskCounter.InstanceName, writeDirectionLabelValue, float64(logicalDiskCounter.Values[avgDiskSecsPerWrite])/1e7) + } +} + +func initializeDiskPendingOperationsMetric(metric pdata.Metric, now pdata.TimestampUnixNano, logicalDiskCounterValues []*perfcounters.CounterValues) { + diskPendingOperationsDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(len(logicalDiskCounterValues)) + for idx, logicalDiskCounter := range logicalDiskCounterValues { + initializeDiskPendingDataPoint(idps.At(idx), now, logicalDiskCounter.InstanceName, logicalDiskCounter.Values[queueLength]) + } +} + +func initializeInt64DataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, deviceLabel string, directionLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + if directionLabel != "" { + labelsMap.Insert(directionLabelName, directionLabel) + } + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func initializeDoubleDataPoint(dataPoint pdata.DoubleDataPoint, startTime, now pdata.TimestampUnixNano, deviceLabel string, directionLabel string, value float64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + if directionLabel != "" { + labelsMap.Insert(directionLabelName, directionLabel) + } + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func initializeDiskPendingDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, deviceLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows_test.go new file mode 100644 index 00000000000..e6940fda432 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows_test.go @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package diskscraper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/perfcounters" +) + +func TestScrape_Error(t *testing.T) { + type testCase struct { + name string + scrapeErr error + getObjectErr error + getValuesErr error + expectedErr string + } + + testCases := []testCase{ + { + name: "scrapeError", + scrapeErr: errors.New("err1"), + expectedErr: "err1", + }, + { + name: "getObjectErr", + getObjectErr: errors.New("err1"), + expectedErr: "err1", + }, + { + name: "getValuesErr", + getValuesErr: errors.New("err1"), + expectedErr: "err1", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper, err := newDiskScraper(context.Background(), &Config{}) + require.NoError(t, err, "Failed to create disk scraper: %v", err) + + scraper.perfCounterScraper = perfcounters.NewMockPerfCounterScraperError(test.scrapeErr, test.getObjectErr, test.getValuesErr) + + err = scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize disk scraper: %v", err) + + _, err = scraper.scrape(context.Background()) + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, metricsLen, err.(consumererror.PartialScrapeError).Failed) + } + }) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory.go new file mode 100644 index 00000000000..caffb4c32d3 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Disk scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "disk" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s, err := newDiskScraper(ctx, cfg) + if err != nil { + return nil, err + } + + ms := scraperhelper.NewMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory_test.go new file mode 100644 index 00000000000..e87592b6e21 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/factory_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diskscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} + +func TestCreateMetricsScraper_Error(t *testing.T) { + factory := &Factory{} + cfg := &Config{Include: MatchConfig{Devices: []string{""}}} + + _, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.Error(t, err) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go new file mode 100644 index 00000000000..b28e08913fa --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go @@ -0,0 +1,140 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filesystemscraper + +import ( + "fmt" + + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +// Config relating to FileSystem Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // IncludeDevices specifies a filter on the devices that should be included in the generated metrics. + IncludeDevices DeviceMatchConfig `mapstructure:"include_devices"` + // ExcludeDevices specifies a filter on the devices that should be excluded from the generated metrics. + ExcludeDevices DeviceMatchConfig `mapstructure:"exclude_devices"` + + // IncludeFSTypes specifies a filter on the filesystem types that should be included in the generated metrics. + IncludeFSTypes FSTypeMatchConfig `mapstructure:"include_fs_types"` + // ExcludeFSTypes specifies a filter on the filesystem types points that should be excluded from the generated metrics. + ExcludeFSTypes FSTypeMatchConfig `mapstructure:"exclude_fs_types"` + + // IncludeMountPoints specifies a filter on the mount points that should be included in the generated metrics. + IncludeMountPoints MountPointMatchConfig `mapstructure:"include_mount_points"` + // ExcludeMountPoints specifies a filter on the mount points that should be excluded from the generated metrics. + ExcludeMountPoints MountPointMatchConfig `mapstructure:"exclude_mount_points"` +} + +type DeviceMatchConfig struct { + filterset.Config `mapstructure:",squash"` + + Devices []string `mapstructure:"devices"` +} + +type FSTypeMatchConfig struct { + filterset.Config `mapstructure:",squash"` + + FSTypes []string `mapstructure:"fs_types"` +} + +type MountPointMatchConfig struct { + filterset.Config `mapstructure:",squash"` + + MountPoints []string `mapstructure:"mount_points"` +} + +type fsFilter struct { + includeDeviceFilter filterset.FilterSet + excludeDeviceFilter filterset.FilterSet + includeFSTypeFilter filterset.FilterSet + excludeFSTypeFilter filterset.FilterSet + includeMountPointFilter filterset.FilterSet + excludeMountPointFilter filterset.FilterSet + filtersExist bool +} + +func (cfg *Config) createFilter() (*fsFilter, error) { + var err error + filter := fsFilter{} + + filter.includeDeviceFilter, err = newIncludeFilterHelper(cfg.IncludeDevices.Devices, &cfg.IncludeDevices.Config, deviceLabelName) + if err != nil { + return nil, err + } + + filter.excludeDeviceFilter, err = newExcludeFilterHelper(cfg.ExcludeDevices.Devices, &cfg.ExcludeDevices.Config, deviceLabelName) + if err != nil { + return nil, err + } + + filter.includeFSTypeFilter, err = newIncludeFilterHelper(cfg.IncludeFSTypes.FSTypes, &cfg.IncludeFSTypes.Config, typeLabelName) + if err != nil { + return nil, err + } + + filter.excludeFSTypeFilter, err = newExcludeFilterHelper(cfg.ExcludeFSTypes.FSTypes, &cfg.ExcludeFSTypes.Config, typeLabelName) + if err != nil { + return nil, err + } + + filter.includeMountPointFilter, err = newIncludeFilterHelper(cfg.IncludeMountPoints.MountPoints, &cfg.IncludeMountPoints.Config, mountPointLabelName) + if err != nil { + return nil, err + } + + filter.excludeMountPointFilter, err = newExcludeFilterHelper(cfg.ExcludeMountPoints.MountPoints, &cfg.ExcludeMountPoints.Config, mountPointLabelName) + if err != nil { + return nil, err + } + + filter.setFiltersExist() + return &filter, nil +} + +func (f *fsFilter) setFiltersExist() { + f.filtersExist = f.includeMountPointFilter != nil || f.excludeMountPointFilter != nil || + f.includeFSTypeFilter != nil || f.excludeFSTypeFilter != nil || + f.includeDeviceFilter != nil || f.excludeDeviceFilter != nil +} + +const ( + excludeKey = "exclude" + includeKey = "include" +) + +func newIncludeFilterHelper(items []string, filterSet *filterset.Config, typ string) (filterset.FilterSet, error) { + return newFilterHelper(items, filterSet, includeKey, typ) +} + +func newExcludeFilterHelper(items []string, filterSet *filterset.Config, typ string) (filterset.FilterSet, error) { + return newFilterHelper(items, filterSet, excludeKey, typ) +} + +func newFilterHelper(items []string, filterSet *filterset.Config, typ string, filterType string) (filterset.FilterSet, error) { + var err error + var filter filterset.FilterSet + + if len(items) > 0 { + filter, err = filterset.CreateFilterSet(items, filterSet) + if err != nil { + return nil, fmt.Errorf("error creating %s %s filters: %w", filterType, typ, err) + } + } + return filter, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory.go new file mode 100644 index 00000000000..04d02fe5262 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory.go @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filesystemscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for FileSystem scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "filesystem" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// Type gets the type of the scraper config created by this Factory. +func (f *Factory) Type() string { + return TypeStr +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s, err := newFileSystemScraper(ctx, cfg) + if err != nil { + return nil, err + } + + ms := scraperhelper.NewMetricsScraper(TypeStr, s.Scrape) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory_test.go new file mode 100644 index 00000000000..7d2712f02bb --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/factory_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filesystemscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} + +func TestCreateMetricsScraper_Error(t *testing.T) { + factory := &Factory{} + cfg := &Config{IncludeDevices: DeviceMatchConfig{Devices: []string{""}}} + + _, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.Error(t, err) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_metadata.go new file mode 100644 index 00000000000..b13a2211685 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_metadata.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filesystemscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// labels + +const ( + deviceLabelName = "device" + mountModeLabelName = "mode" + mountPointLabelName = "mountpoint" + stateLabelName = "state" + typeLabelName = "type" +) + +// state label values + +const ( + freeLabelValue = "free" + reservedLabelValue = "reserved" + usedLabelValue = "used" +) + +// descriptors + +var fileSystemUsageDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.filesystem.usage") + metric.SetDescription("Filesystem bytes used.") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var fileSystemINodesUsageDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.filesystem.inodes.usage") + metric.SetDescription("FileSystem iNodes used.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go new file mode 100644 index 00000000000..cbe32859820 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go @@ -0,0 +1,167 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filesystemscraper + +import ( + "context" + "strings" + "time" + + "github.com/shirou/gopsutil/disk" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +const ( + standardMetricsLen = 1 + metricsLen = standardMetricsLen + systemSpecificMetricsLen +) + +// scraper for FileSystem Metrics +type scraper struct { + config *Config + fsFilter fsFilter + + // for mocking gopsutil disk.Partitions & disk.Usage + partitions func(bool) ([]disk.PartitionStat, error) + usage func(string) (*disk.UsageStat, error) +} + +type deviceUsage struct { + partition disk.PartitionStat + usage *disk.UsageStat +} + +// newFileSystemScraper creates a FileSystem Scraper +func newFileSystemScraper(_ context.Context, cfg *Config) (*scraper, error) { + fsFilter, err := cfg.createFilter() + if err != nil { + return nil, err + } + + scraper := &scraper{config: cfg, partitions: disk.Partitions, usage: disk.Usage, fsFilter: *fsFilter} + return scraper, nil +} + +// Scrape +func (s *scraper) Scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + now := internal.TimeToUnixNano(time.Now()) + + // omit logical (virtual) filesystems (not relevant for windows) + partitions, err := s.partitions( /*all=*/ false) + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + var errors []error + usages := make([]*deviceUsage, 0, len(partitions)) + for _, partition := range partitions { + if !s.fsFilter.includePartition(partition) { + continue + } + usage, usageErr := s.usage(partition.Mountpoint) + if usageErr != nil { + errors = append(errors, consumererror.NewPartialScrapeError(usageErr, 0)) + continue + } + + usages = append(usages, &deviceUsage{partition, usage}) + } + + if len(usages) > 0 { + metrics.Resize(metricsLen) + initializeFileSystemUsageMetric(metrics.At(0), now, usages) + appendSystemSpecificMetrics(metrics, 1, now, usages) + } + + err = scraperhelper.CombineScrapeErrors(errors) + if err != nil && len(usages) == 0 { + partialErr := err.(consumererror.PartialScrapeError) + partialErr.Failed = metricsLen + err = partialErr + } + + return metrics, err +} + +func initializeFileSystemUsageMetric(metric pdata.Metric, now pdata.TimestampUnixNano, deviceUsages []*deviceUsage) { + fileSystemUsageDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(fileSystemStatesLen * len(deviceUsages)) + for i, deviceUsage := range deviceUsages { + appendFileSystemUsageStateDataPoints(idps, i*fileSystemStatesLen, now, deviceUsage) + } +} + +func initializeFileSystemUsageDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, partition disk.PartitionStat, stateLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, partition.Device) + labelsMap.Insert(typeLabelName, partition.Fstype) + labelsMap.Insert(mountModeLabelName, getMountMode(partition.Opts)) + labelsMap.Insert(mountPointLabelName, partition.Mountpoint) + labelsMap.Insert(stateLabelName, stateLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func getMountMode(opts string) string { + splitOptions := strings.Split(opts, ",") + if exists(splitOptions, "rw") { + return "rw" + } else if exists(splitOptions, "ro") { + return "ro" + } + return "unknown" +} + +func exists(options []string, opt string) bool { + for _, o := range options { + if o == opt { + return true + } + } + return false +} + +func (f *fsFilter) includePartition(partition disk.PartitionStat) bool { + // If filters do not exist, return early. + if !f.filtersExist || (f.includeDevice(partition.Device) && + f.includeFSType(partition.Fstype) && + f.includeMountPoint(partition.Mountpoint)) { + return true + } + return false +} + +func (f *fsFilter) includeDevice(deviceName string) bool { + return (f.includeDeviceFilter == nil || f.includeDeviceFilter.Matches(deviceName)) && + (f.excludeDeviceFilter == nil || !f.excludeDeviceFilter.Matches(deviceName)) +} + +func (f *fsFilter) includeFSType(fsType string) bool { + return (f.includeFSTypeFilter == nil || f.includeFSTypeFilter.Matches(fsType)) && + (f.excludeFSTypeFilter == nil || !f.excludeFSTypeFilter.Matches(fsType)) +} + +func (f *fsFilter) includeMountPoint(mountPoint string) bool { + return (f.includeMountPointFilter == nil || f.includeMountPointFilter.Matches(mountPoint)) && + (f.excludeMountPointFilter == nil || !f.excludeMountPointFilter.Matches(mountPoint)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_others.go new file mode 100644 index 00000000000..f8365a5be97 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_others.go @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux,!darwin,!freebsd,!openbsd,!solaris + +package filesystemscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +const fileSystemStatesLen = 2 + +func appendFileSystemUsageStateDataPoints(idps pdata.IntDataPointSlice, startIdx int, now pdata.TimestampUnixNano, deviceUsage *deviceUsage) { + initializeFileSystemUsageDataPoint(idps.At(startIdx+0), now, deviceUsage.partition, usedLabelValue, int64(deviceUsage.usage.Used)) + initializeFileSystemUsageDataPoint(idps.At(startIdx+1), now, deviceUsage.partition, freeLabelValue, int64(deviceUsage.usage.Free)) +} + +const systemSpecificMetricsLen = 0 + +func appendSystemSpecificMetrics(metrics pdata.MetricSlice, startIdx int, now pdata.TimestampUnixNano, deviceUsages []*deviceUsage) { +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go new file mode 100644 index 00000000000..12d2aee3365 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go @@ -0,0 +1,288 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filesystemscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/shirou/gopsutil/disk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + config Config + partitionsFunc func(bool) ([]disk.PartitionStat, error) + usageFunc func(string) (*disk.UsageStat, error) + expectMetrics bool + expectedDeviceDataPoints int + expectedDeviceLabelValues []map[string]string + newErrRegex string + expectedErr string + } + + testCases := []testCase{ + { + name: "Standard", + expectMetrics: true, + }, + { + name: "Include single device filter", + config: Config{IncludeDevices: DeviceMatchConfig{filterset.Config{MatchType: "strict"}, []string{"a"}}}, + partitionsFunc: func(bool) ([]disk.PartitionStat, error) { + return []disk.PartitionStat{{Device: "a"}, {Device: "b"}}, nil + }, + usageFunc: func(string) (*disk.UsageStat, error) { + return &disk.UsageStat{}, nil + }, + expectMetrics: true, + expectedDeviceDataPoints: 1, + }, + { + name: "Include Device Filter that matches nothing", + config: Config{IncludeDevices: DeviceMatchConfig{filterset.Config{MatchType: "strict"}, []string{"@*^#&*$^#)"}}}, + expectMetrics: false, + }, + { + name: "Include filter with devices, filesystem type and mount points", + config: Config{ + IncludeDevices: DeviceMatchConfig{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Devices: []string{"device_a", "device_b"}, + }, + ExcludeFSTypes: FSTypeMatchConfig{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + FSTypes: []string{"fs_type_b"}, + }, + ExcludeMountPoints: MountPointMatchConfig{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + MountPoints: []string{"mount_point_b", "mount_point_c"}, + }, + }, + usageFunc: func(s string) (*disk.UsageStat, error) { + return &disk.UsageStat{ + Fstype: "fs_type_a", + }, nil + }, + partitionsFunc: func(b bool) ([]disk.PartitionStat, error) { + return []disk.PartitionStat{ + { + Device: "device_a", + Mountpoint: "mount_point_a", + Fstype: "fs_type_a", + }, + { + Device: "device_a", + Mountpoint: "mount_point_b", + Fstype: "fs_type_b", + }, + { + Device: "device_b", + Mountpoint: "mount_point_c", + Fstype: "fs_type_b", + }, + { + Device: "device_b", + Mountpoint: "mount_point_d", + Fstype: "fs_type_c", + }, + }, nil + }, + expectMetrics: true, + expectedDeviceDataPoints: 2, + expectedDeviceLabelValues: []map[string]string{ + { + "device": "device_a", + "mountpoint": "mount_point_a", + "type": "fs_type_a", + "mode": "unknown", + }, + { + "device": "device_b", + "mountpoint": "mount_point_d", + "type": "fs_type_c", + "mode": "unknown", + }, + }, + }, + { + name: "Invalid Include Device Filter", + config: Config{IncludeDevices: DeviceMatchConfig{Devices: []string{"test"}}}, + newErrRegex: "^error creating device include filters:", + }, + { + name: "Invalid Exclude Device Filter", + config: Config{ExcludeDevices: DeviceMatchConfig{Devices: []string{"test"}}}, + newErrRegex: "^error creating device exclude filters:", + }, + { + name: "Invalid Include Filesystems Filter", + config: Config{IncludeFSTypes: FSTypeMatchConfig{FSTypes: []string{"test"}}}, + newErrRegex: "^error creating type include filters:", + }, + { + name: "Invalid Exclude Filesystems Filter", + config: Config{ExcludeFSTypes: FSTypeMatchConfig{FSTypes: []string{"test"}}}, + newErrRegex: "^error creating type exclude filters:", + }, + { + name: "Invalid Include Moountpoints Filter", + config: Config{IncludeMountPoints: MountPointMatchConfig{MountPoints: []string{"test"}}}, + newErrRegex: "^error creating mountpoint include filters:", + }, + { + name: "Invalid Exclude Moountpoints Filter", + config: Config{ExcludeMountPoints: MountPointMatchConfig{MountPoints: []string{"test"}}}, + newErrRegex: "^error creating mountpoint exclude filters:", + }, + { + name: "Partitions Error", + partitionsFunc: func(bool) ([]disk.PartitionStat, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + }, + { + name: "Usage Error", + usageFunc: func(string) (*disk.UsageStat, error) { return nil, errors.New("err2") }, + expectedErr: "err2", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper, err := newFileSystemScraper(context.Background(), &test.config) + if test.newErrRegex != "" { + require.Error(t, err) + require.Regexp(t, test.newErrRegex, err) + return + } + require.NoError(t, err, "Failed to create file system scraper: %v", err) + + if test.partitionsFunc != nil { + scraper.partitions = test.partitionsFunc + } + if test.usageFunc != nil { + scraper.usage = test.usageFunc + } + + metrics, err := scraper.Scrape(context.Background()) + if test.expectedErr != "" { + assert.Contains(t, err.Error(), test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, metricsLen, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + if !test.expectMetrics { + assert.Equal(t, 0, metrics.Len()) + return + } + + assert.GreaterOrEqual(t, metrics.Len(), 1) + + assertFileSystemUsageMetricValid( + t, + metrics.At(0), + fileSystemUsageDescriptor, + test.expectedDeviceDataPoints*fileSystemStatesLen, + test.expectedDeviceLabelValues, + ) + + if isUnix() { + assertFileSystemUsageMetricHasUnixSpecificStateLabels(t, metrics.At(0)) + assertFileSystemUsageMetricValid( + t, + metrics.At(1), + fileSystemINodesUsageDescriptor, + test.expectedDeviceDataPoints*2, + test.expectedDeviceLabelValues, + ) + } + + internal.AssertSameTimeStampForAllMetrics(t, metrics) + }) + } +} + +func assertFileSystemUsageMetricValid( + t *testing.T, + metric pdata.Metric, + descriptor pdata.Metric, + expectedDeviceDataPoints int, + expectedDeviceLabelValues []map[string]string) { + internal.AssertDescriptorEqual(t, descriptor, metric) + for i := 0; i < metric.IntSum().DataPoints().Len(); i++ { + for _, label := range []string{deviceLabelName, typeLabelName, mountModeLabelName, mountPointLabelName} { + internal.AssertIntSumMetricLabelExists(t, metric, i, label) + } + } + + if expectedDeviceDataPoints > 0 { + assert.Equal(t, expectedDeviceDataPoints, metric.IntSum().DataPoints().Len()) + + // Assert label values if specified. + if expectedDeviceLabelValues != nil { + dpsPerDevice := expectedDeviceDataPoints / len(expectedDeviceLabelValues) + deviceIdx := 0 + for i := 0; i < metric.IntSum().DataPoints().Len(); i += dpsPerDevice { + for j := i; j < i+dpsPerDevice; j++ { + for labelKey, labelValue := range expectedDeviceLabelValues[deviceIdx] { + internal.AssertIntSumMetricLabelHasValue(t, metric, j, labelKey, labelValue) + } + } + deviceIdx++ + } + } + } else { + assert.GreaterOrEqual(t, metric.IntSum().DataPoints().Len(), fileSystemStatesLen) + } + internal.AssertIntSumMetricLabelHasValue(t, metric, 0, stateLabelName, usedLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, metric, 1, stateLabelName, freeLabelValue) +} + +func assertFileSystemUsageMetricHasUnixSpecificStateLabels(t *testing.T, metric pdata.Metric) { + internal.AssertIntSumMetricLabelHasValue(t, metric, 2, stateLabelName, reservedLabelValue) +} + +func isUnix() bool { + for _, unixOS := range []string{"linux", "darwin", "freebsd", "openbsd", "solaris"} { + if runtime.GOOS == unixOS { + return true + } + } + + return false +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_unix.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_unix.go new file mode 100644 index 00000000000..fafe5ec8264 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_unix.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux darwin freebsd openbsd solaris + +package filesystemscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +const fileSystemStatesLen = 3 + +func appendFileSystemUsageStateDataPoints(idps pdata.IntDataPointSlice, startIdx int, now pdata.TimestampUnixNano, deviceUsage *deviceUsage) { + initializeFileSystemUsageDataPoint(idps.At(startIdx+0), now, deviceUsage.partition, usedLabelValue, int64(deviceUsage.usage.Used)) + initializeFileSystemUsageDataPoint(idps.At(startIdx+1), now, deviceUsage.partition, freeLabelValue, int64(deviceUsage.usage.Free)) + initializeFileSystemUsageDataPoint(idps.At(startIdx+2), now, deviceUsage.partition, reservedLabelValue, int64(deviceUsage.usage.Total-deviceUsage.usage.Used-deviceUsage.usage.Free)) +} + +const systemSpecificMetricsLen = 1 + +func appendSystemSpecificMetrics(metrics pdata.MetricSlice, startIdx int, now pdata.TimestampUnixNano, deviceUsages []*deviceUsage) { + metric := metrics.At(startIdx) + fileSystemINodesUsageDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(deviceUsages)) + for idx, deviceUsage := range deviceUsages { + startIndex := 2 * idx + initializeFileSystemUsageDataPoint(idps.At(startIndex+0), now, deviceUsage.partition, usedLabelValue, int64(deviceUsage.usage.InodesUsed)) + initializeFileSystemUsageDataPoint(idps.At(startIndex+1), now, deviceUsage.partition, freeLabelValue, int64(deviceUsage.usage.InodesFree)) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go new file mode 100644 index 00000000000..3f3d35fb8b6 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loadscraper + +import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + +// Config relating to Load Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory.go new file mode 100644 index 00000000000..fbb6e5a593d --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loadscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Load scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "load" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + logger *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s := newLoadScraper(ctx, logger, cfg) + + ms := scraperhelper.NewMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + scraperhelper.WithShutdown(s.shutdown), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory_test.go new file mode 100644 index 00000000000..d457c4a4662 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/factory_test.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loadscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_metadata.go new file mode 100644 index 00000000000..f52437a1611 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_metadata.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loadscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// descriptors + +var loadAvg1MDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.cpu.load_average.1m") + metric.SetDescription("Average CPU Load over 1 minute.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeDoubleGauge) + return metric +}() + +var loadAvg5mDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.cpu.load_average.5m") + metric.SetDescription("Average CPU Load over 5 minutes.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeDoubleGauge) + return metric +}() + +var loadAvg15mDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.cpu.load_average.15m") + metric.SetDescription("Average CPU Load over 15 minutes.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeDoubleGauge) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper.go new file mode 100644 index 00000000000..d13b7540b66 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loadscraper + +import ( + "context" + "time" + + "github.com/shirou/gopsutil/load" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +const metricsLen = 3 + +// scraper for Load Metrics +type scraper struct { + logger *zap.Logger + config *Config + + // for mocking + load func() (*load.AvgStat, error) +} + +// newLoadScraper creates a set of Load related metrics +func newLoadScraper(_ context.Context, logger *zap.Logger, cfg *Config) *scraper { + return &scraper{logger: logger, config: cfg, load: getSampledLoadAverages} +} + +// start +func (s *scraper) start(ctx context.Context, _ component.Host) error { + return startSampling(ctx, s.logger) +} + +// shutdown +func (s *scraper) shutdown(ctx context.Context) error { + return stopSampling(ctx) +} + +// scrape +func (s *scraper) scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + now := internal.TimeToUnixNano(time.Now()) + avgLoadValues, err := s.load() + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + metrics.Resize(metricsLen) + initializeLoadMetric(metrics.At(0), loadAvg1MDescriptor, now, avgLoadValues.Load1) + initializeLoadMetric(metrics.At(1), loadAvg5mDescriptor, now, avgLoadValues.Load5) + initializeLoadMetric(metrics.At(2), loadAvg15mDescriptor, now, avgLoadValues.Load15) + return metrics, nil +} + +func initializeLoadMetric(metric pdata.Metric, metricDescriptor pdata.Metric, now pdata.TimestampUnixNano, value float64) { + metricDescriptor.CopyTo(metric) + + idps := metric.DoubleGauge().DataPoints() + idps.Resize(1) + dp := idps.At(0) + dp.SetTimestamp(now) + dp.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_others.go new file mode 100644 index 00000000000..5b0c2afcbd4 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_others.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package loadscraper + +import ( + "context" + + "github.com/shirou/gopsutil/load" + "go.uber.org/zap" +) + +// unix based systems sample & compute load averages in the kernel, so nothing to do here +func startSampling(_ context.Context, _ *zap.Logger) error { + return nil +} + +func stopSampling(_ context.Context) error { + return nil +} + +func getSampledLoadAverages() (*load.AvgStat, error) { + return load.Avg() +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_test.go new file mode 100644 index 00000000000..0e5aaae219a --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_test.go @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loadscraper + +import ( + "context" + "errors" + "testing" + + "github.com/shirou/gopsutil/load" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + loadFunc func() (*load.AvgStat, error) + expectedErr string + } + + testCases := []testCase{ + { + name: "Standard", + }, + { + name: "Load Error", + loadFunc: func() (*load.AvgStat, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newLoadScraper(context.Background(), zap.NewNop(), &Config{}) + if test.loadFunc != nil { + scraper.load = test.loadFunc + } + + err := scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize load scraper: %v", err) + defer func() { assert.NoError(t, scraper.shutdown(context.Background())) }() + + metrics, err := scraper.scrape(context.Background()) + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, metricsLen, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + // expect 3 metrics + assert.Equal(t, 3, metrics.Len()) + + // expect a single datapoint for 1m, 5m & 15m load metrics + assertMetricHasSingleDatapoint(t, metrics.At(0), loadAvg1MDescriptor) + assertMetricHasSingleDatapoint(t, metrics.At(1), loadAvg5mDescriptor) + assertMetricHasSingleDatapoint(t, metrics.At(2), loadAvg15mDescriptor) + + internal.AssertSameTimeStampForAllMetrics(t, metrics) + }) + } +} + +func assertMetricHasSingleDatapoint(t *testing.T, metric pdata.Metric, descriptor pdata.Metric) { + internal.AssertDescriptorEqual(t, descriptor, metric) + assert.Equal(t, 1, metric.DoubleGauge().DataPoints().Len()) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows.go new file mode 100644 index 00000000000..2dddfba7fc6 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows.go @@ -0,0 +1,169 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package loadscraper + +import ( + "context" + "math" + "sync" + "time" + + "github.com/shirou/gopsutil/load" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/perfcounters" +) + +// Sample processor queue length at a 5s frequency, and calculate exponentially weighted moving averages +// as per https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation + +const ( + system = "System" + processorQueueLength = "Processor Queue Length" +) + +var ( + samplingFrequency = 5 * time.Second + + loadAvgFactor1m = 1 / math.Exp(samplingFrequency.Seconds()/time.Minute.Seconds()) + loadAvgFactor5m = 1 / math.Exp(samplingFrequency.Seconds()/(5*time.Minute).Seconds()) + loadAvgFactor15m = 1 / math.Exp(samplingFrequency.Seconds()/(15*time.Minute).Seconds()) +) + +var ( + scraperCount int + startupLock sync.Mutex + + samplerInstance *sampler +) + +type sampler struct { + done chan struct{} + logger *zap.Logger + perfCounterScraper perfcounters.PerfCounterScraper + loadAvg1m float64 + loadAvg5m float64 + loadAvg15m float64 + lock sync.RWMutex +} + +func startSampling(_ context.Context, logger *zap.Logger) error { + startupLock.Lock() + defer startupLock.Unlock() + + // startSampling may be called multiple times if multiple scrapers are + // initialized - but we only want to initialize a single load sampler + scraperCount++ + if scraperCount > 1 { + return nil + } + + var err error + samplerInstance, err = newSampler(logger) + if err != nil { + return err + } + + samplerInstance.startSamplingTicker() + return nil +} + +func newSampler(logger *zap.Logger) (*sampler, error) { + perfCounterScraper := &perfcounters.PerfLibScraper{} + if err := perfCounterScraper.Initialize(system); err != nil { + return nil, err + } + + sampler := &sampler{ + logger: logger, + perfCounterScraper: perfCounterScraper, + done: make(chan struct{}), + } + + return sampler, nil +} + +func (sw *sampler) startSamplingTicker() { + go func() { + ticker := time.NewTicker(samplingFrequency) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + sw.sampleLoad() + case <-sw.done: + return + } + } + }() +} + +func (sw *sampler) sampleLoad() { + counters, err := sw.perfCounterScraper.Scrape() + if err != nil { + sw.logger.Error("Load Scraper: failed to measure processor queue length", zap.Error(err)) + return + } + + systemObject, err := counters.GetObject(system) + if err != nil { + sw.logger.Error("Load Scraper: failed to measure processor queue length", zap.Error(err)) + return + } + + counterValues, err := systemObject.GetValues(processorQueueLength) + if err != nil { + sw.logger.Error("Load Scraper: failed to measure processor queue length", zap.Error(err)) + return + } + + currentLoad := float64(counterValues[0].Values[processorQueueLength]) + + sw.lock.Lock() + defer sw.lock.Unlock() + sw.loadAvg1m = sw.loadAvg1m*loadAvgFactor1m + currentLoad*(1-loadAvgFactor1m) + sw.loadAvg5m = sw.loadAvg5m*loadAvgFactor5m + currentLoad*(1-loadAvgFactor5m) + sw.loadAvg15m = sw.loadAvg15m*loadAvgFactor15m + currentLoad*(1-loadAvgFactor15m) +} + +func stopSampling(_ context.Context) error { + startupLock.Lock() + defer startupLock.Unlock() + + // only stop sampling if all load scrapers have been closed + scraperCount-- + if scraperCount > 0 { + return nil + } + + close(samplerInstance.done) + return nil +} + +func getSampledLoadAverages() (*load.AvgStat, error) { + samplerInstance.lock.RLock() + defer samplerInstance.lock.RUnlock() + + avgStat := &load.AvgStat{ + Load1: samplerInstance.loadAvg1m, + Load5: samplerInstance.loadAvg5m, + Load15: samplerInstance.loadAvg15m, + } + + return avgStat, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows_test.go new file mode 100644 index 00000000000..9966af8a6c4 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper/load_scraper_windows_test.go @@ -0,0 +1,124 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package loadscraper + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/perfcounters" +) + +func TestStartSampling(t *testing.T) { + // override sampling frequency to 2ms + samplingFrequency = 2 * time.Millisecond + + // startSampling should set up perf counter and start sampling + startSampling(context.Background(), zap.NewNop()) + assertSamplingUnderway(t) + + // override the processor queue length perf counter with a mock + // that will ensure a positive value is returned + assert.IsType(t, &perfcounters.PerfLibScraper{}, samplerInstance.perfCounterScraper) + samplerInstance.perfCounterScraper = perfcounters.NewMockPerfCounterScraper(map[string]map[string][]int64{ + system: {processorQueueLength: {100}}, + }) + + // second call to startSampling should succeed, but not do anything + startSampling(context.Background(), zap.NewNop()) + assertSamplingUnderway(t) + assert.IsType(t, &perfcounters.MockPerfCounterScraper{}, samplerInstance.perfCounterScraper) + + // ensure that a positive load avg is returned by a call to + // "getSampledLoadAverages" which validates the value from the + // mock perf counter was used + require.Eventually(t, func() bool { + avgLoadValues, err := getSampledLoadAverages() + assert.NoError(t, err) + return avgLoadValues.Load1 > 0 && avgLoadValues.Load5 > 0 && avgLoadValues.Load15 > 0 + }, time.Second, time.Millisecond, "Load Avg was not set after 1s") + + // sampling should continue after first call to stopSampling since + // startSampling was called twice + stopSampling(context.Background()) + assertSamplingUnderway(t) + + // second call to stopSampling should close perf counter, stop + // sampling, and clean up the sampler + stopSampling(context.Background()) + assertSamplingStopped(t) +} + +func assertSamplingUnderway(t *testing.T) { + assert.NotNil(t, samplerInstance) + assert.NotNil(t, samplerInstance.perfCounterScraper) + + select { + case <-samplerInstance.done: + assert.Fail(t, "Load scraper sampling done channel unexpectedly closed") + default: + } +} + +func assertSamplingStopped(t *testing.T) { + select { + case <-samplerInstance.done: + default: + assert.Fail(t, "Load scraper sampling done channel not closed") + } +} + +func TestSampleLoad(t *testing.T) { + counterReturnValues := []int64{10, 20, 30, 40, 50} + mockPerfCounterScraper := perfcounters.NewMockPerfCounterScraper(map[string]map[string][]int64{ + system: {processorQueueLength: counterReturnValues}, + }) + + samplerInstance = &sampler{perfCounterScraper: mockPerfCounterScraper} + + for i := 0; i < len(counterReturnValues); i++ { + samplerInstance.sampleLoad() + } + + assert.Equal(t, calcExpectedLoad(counterReturnValues, loadAvgFactor1m), samplerInstance.loadAvg1m) + assert.Equal(t, calcExpectedLoad(counterReturnValues, loadAvgFactor5m), samplerInstance.loadAvg5m) + assert.Equal(t, calcExpectedLoad(counterReturnValues, loadAvgFactor15m), samplerInstance.loadAvg15m) +} + +func calcExpectedLoad(scrapedValues []int64, loadAvgFactor float64) float64 { + // replicate the calculations that should be performed to determine the exponentially + // weighted moving averages based on the specified scraped values + var expectedLoad float64 + for i := 0; i < len(scrapedValues); i++ { + expectedLoad = expectedLoad*loadAvgFactor + float64(scrapedValues[i])*(1-loadAvgFactor) + } + return expectedLoad +} + +func Benchmark_SampleLoad(b *testing.B) { + s, _ := newSampler(zap.NewNop()) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + s.sampleLoad() + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go new file mode 100644 index 00000000000..3f5f5c6a853 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memoryscraper + +import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + +// Config relating to Memory Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory.go new file mode 100644 index 00000000000..94aefcacf97 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory.go @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memoryscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Memory scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "memory" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s := newMemoryScraper(ctx, cfg) + + ms := scraperhelper.NewMetricsScraper(TypeStr, s.Scrape) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory_test.go new file mode 100644 index 00000000000..3ca86050ac5 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/factory_test.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memoryscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper.go new file mode 100644 index 00000000000..16b49dc725c --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper.go @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memoryscraper + +import ( + "context" + "time" + + "github.com/shirou/gopsutil/mem" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const metricsLen = 1 + +// scraper for Memory Metrics +type scraper struct { + config *Config + + // for mocking gopsutil mem.VirtualMemory + virtualMemory func() (*mem.VirtualMemoryStat, error) +} + +// newMemoryScraper creates a Memory Scraper +func newMemoryScraper(_ context.Context, cfg *Config) *scraper { + return &scraper{config: cfg, virtualMemory: mem.VirtualMemory} +} + +// Scrape +func (s *scraper) Scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + now := internal.TimeToUnixNano(time.Now()) + memInfo, err := s.virtualMemory() + if err != nil { + return metrics, consumererror.NewPartialScrapeError(err, metricsLen) + } + + metrics.Resize(metricsLen) + initializeMemoryUsageMetric(metrics.At(0), now, memInfo) + return metrics, nil +} + +func initializeMemoryUsageMetric(metric pdata.Metric, now pdata.TimestampUnixNano, memInfo *mem.VirtualMemoryStat) { + metadata.Metrics.SystemMemoryUsage.New().CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(memStatesLen) + appendMemoryUsageStateDataPoints(idps, now, memInfo) +} + +func initializeMemoryUsageDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, stateLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(metadata.Labels.MemState, stateLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_linux.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_linux.go new file mode 100644 index 00000000000..350ba887c27 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_linux.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package memoryscraper + +import ( + "github.com/shirou/gopsutil/mem" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const memStatesLen = 6 + +func appendMemoryUsageStateDataPoints(idps pdata.IntDataPointSlice, now pdata.TimestampUnixNano, memInfo *mem.VirtualMemoryStat) { + initializeMemoryUsageDataPoint(idps.At(0), now, metadata.LabelMemState.Used, int64(memInfo.Used)) + initializeMemoryUsageDataPoint(idps.At(1), now, metadata.LabelMemState.Free, int64(memInfo.Free)) + initializeMemoryUsageDataPoint(idps.At(2), now, metadata.LabelMemState.Buffered, int64(memInfo.Buffers)) + initializeMemoryUsageDataPoint(idps.At(3), now, metadata.LabelMemState.Cached, int64(memInfo.Cached)) + initializeMemoryUsageDataPoint(idps.At(4), now, metadata.LabelMemState.SlabReclaimable, int64(memInfo.SReclaimable)) + initializeMemoryUsageDataPoint(idps.At(5), now, metadata.LabelMemState.SlabUnreclaimable, int64(memInfo.SUnreclaim)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_others.go new file mode 100644 index 00000000000..474a5aa4cf4 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_others.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux,!windows + +package memoryscraper + +import ( + "github.com/shirou/gopsutil/mem" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const memStatesLen = 3 + +func appendMemoryUsageStateDataPoints(idps pdata.IntDataPointSlice, now pdata.TimestampUnixNano, memInfo *mem.VirtualMemoryStat) { + initializeMemoryUsageDataPoint(idps.At(0), now, metadata.LabelMemState.Used, int64(memInfo.Used)) + initializeMemoryUsageDataPoint(idps.At(1), now, metadata.LabelMemState.Free, int64(memInfo.Free)) + initializeMemoryUsageDataPoint(idps.At(2), now, metadata.LabelMemState.Inactive, int64(memInfo.Inactive)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_test.go new file mode 100644 index 00000000000..8b2b1a6a0c7 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_test.go @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package memoryscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/shirou/gopsutil/mem" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + virtualMemoryFunc func() (*mem.VirtualMemoryStat, error) + expectedErr string + } + + testCases := []testCase{ + { + name: "Standard", + }, + { + name: "Error", + virtualMemoryFunc: func() (*mem.VirtualMemoryStat, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newMemoryScraper(context.Background(), &Config{}) + if test.virtualMemoryFunc != nil { + scraper.virtualMemory = test.virtualMemoryFunc + } + + metrics, err := scraper.Scrape(context.Background()) + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, metricsLen, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + assert.Equal(t, 1, metrics.Len()) + + assertMemoryUsageMetricValid(t, metrics.At(0), metadata.Metrics.SystemMemoryUsage.New()) + + if runtime.GOOS == "linux" { + assertMemoryUsageMetricHasLinuxSpecificStateLabels(t, metrics.At(0)) + } else if runtime.GOOS != "windows" { + internal.AssertIntSumMetricLabelHasValue(t, metrics.At(0), 2, metadata.Labels.MemState, metadata.LabelMemState.Inactive) + } + + internal.AssertSameTimeStampForAllMetrics(t, metrics) + }) + } +} + +func assertMemoryUsageMetricValid(t *testing.T, metric pdata.Metric, descriptor pdata.Metric) { + internal.AssertDescriptorEqual(t, descriptor, metric) + assert.GreaterOrEqual(t, metric.IntSum().DataPoints().Len(), 2) + internal.AssertIntSumMetricLabelHasValue(t, metric, 0, metadata.Labels.MemState, metadata.LabelMemState.Used) + internal.AssertIntSumMetricLabelHasValue(t, metric, 1, metadata.Labels.MemState, metadata.LabelMemState.Free) +} + +func assertMemoryUsageMetricHasLinuxSpecificStateLabels(t *testing.T, metric pdata.Metric) { + internal.AssertIntSumMetricLabelHasValue(t, metric, 2, metadata.Labels.MemState, metadata.LabelMemState.Buffered) + internal.AssertIntSumMetricLabelHasValue(t, metric, 3, metadata.Labels.MemState, metadata.LabelMemState.Cached) + internal.AssertIntSumMetricLabelHasValue(t, metric, 4, metadata.Labels.MemState, metadata.LabelMemState.SlabReclaimable) + internal.AssertIntSumMetricLabelHasValue(t, metric, 5, metadata.Labels.MemState, metadata.LabelMemState.SlabUnreclaimable) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_windows.go new file mode 100644 index 00000000000..e3d1c12b984 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/memory_scraper_windows.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package memoryscraper + +import ( + "github.com/shirou/gopsutil/mem" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata" +) + +const memStatesLen = 2 + +func appendMemoryUsageStateDataPoints(idps pdata.IntDataPointSlice, now pdata.TimestampUnixNano, memInfo *mem.VirtualMemoryStat) { + initializeMemoryUsageDataPoint(idps.At(0), now, metadata.LabelMemState.Used, int64(memInfo.Used)) + initializeMemoryUsageDataPoint(idps.At(1), now, metadata.LabelMemState.Free, int64(memInfo.Available)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go new file mode 100644 index 00000000000..c63f5fb7b45 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkscraper + +import ( + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +// Config relating to Network Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // Include specifies a filter on the network interfaces that should be included from the generated metrics. + Include MatchConfig `mapstructure:"include"` + // Exclude specifies a filter on the network interfaces that should be excluded from the generated metrics. + Exclude MatchConfig `mapstructure:"exclude"` +} + +type MatchConfig struct { + filterset.Config `mapstructure:",squash"` + + Interfaces []string `mapstructure:"interfaces"` +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory.go new file mode 100644 index 00000000000..dfc97aec205 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Network scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "network" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s, err := newNetworkScraper(ctx, cfg) + if err != nil { + return nil, err + } + + ms := scraperhelper.NewMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory_test.go new file mode 100644 index 00000000000..f47ac841226 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} + +func TestCreateMetricsScraper_Error(t *testing.T) { + factory := &Factory{} + cfg := &Config{Include: MatchConfig{Interfaces: []string{""}}} + + _, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.Error(t, err) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_metadata.go new file mode 100644 index 00000000000..62e51130563 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_metadata.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// network metric constants + +const ( + interfaceLabelName = "interface" + directionLabelName = "direction" + stateLabelName = "state" +) + +// direction label values + +const ( + receiveDirectionLabelValue = "receive" + transmitDirectionLabelValue = "transmit" +) + +// descriptors + +var networkPacketsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.network.packets") + metric.SetDescription("The number of packets transferred.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var networkDroppedPacketsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.network.dropped_packets") + metric.SetDescription("The number of packets dropped.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var networkErrorsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.network.errors") + metric.SetDescription("The number of errors encountered") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var networkIODescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.network.io") + metric.SetDescription("The number of bytes transmitted and received") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var networkTCPConnectionsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.network.tcp_connections") + metric.SetDescription("The number of tcp connections") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_others.go new file mode 100644 index 00000000000..045e545b214 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_others.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package networkscraper + +var allTCPStates = []string{ + "CLOSE_WAIT", + "CLOSE", + "CLOSING", + "DELETE", + "ESTABLISHED", + "FIN_WAIT_1", + "FIN_WAIT_2", + "LAST_ACK", + "LISTEN", + "SYN_SENT", + "SYN_RECV", + "TIME_WAIT", +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper.go new file mode 100644 index 00000000000..753b2723c70 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper.go @@ -0,0 +1,244 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkscraper + +import ( + "context" + "fmt" + "time" + + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/net" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +const ( + networkMetricsLen = 4 + connectionsMetricsLen = 1 +) + +// scraper for Network Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + includeFS filterset.FilterSet + excludeFS filterset.FilterSet + + // for mocking + bootTime func() (uint64, error) + ioCounters func(bool) ([]net.IOCountersStat, error) + connections func(string) ([]net.ConnectionStat, error) +} + +// newNetworkScraper creates a set of Network related metrics +func newNetworkScraper(_ context.Context, cfg *Config) (*scraper, error) { + scraper := &scraper{config: cfg, bootTime: host.BootTime, ioCounters: net.IOCounters, connections: net.Connections} + + var err error + + if len(cfg.Include.Interfaces) > 0 { + scraper.includeFS, err = filterset.CreateFilterSet(cfg.Include.Interfaces, &cfg.Include.Config) + if err != nil { + return nil, fmt.Errorf("error creating network interface include filters: %w", err) + } + } + + if len(cfg.Exclude.Interfaces) > 0 { + scraper.excludeFS, err = filterset.CreateFilterSet(cfg.Exclude.Interfaces, &cfg.Exclude.Config) + if err != nil { + return nil, fmt.Errorf("error creating network interface exclude filters: %w", err) + } + } + + return scraper, nil +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + return nil +} + +func (s *scraper) scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + var errors []error + + err := s.scrapeAndAppendNetworkCounterMetrics(metrics, s.startTime) + if err != nil { + errors = append(errors, err) + } + + err = s.scrapeAndAppendNetworkTCPConnectionsMetric(metrics) + if err != nil { + errors = append(errors, err) + } + + return metrics, scraperhelper.CombineScrapeErrors(errors) +} + +func (s *scraper) scrapeAndAppendNetworkCounterMetrics(metrics pdata.MetricSlice, startTime pdata.TimestampUnixNano) error { + now := internal.TimeToUnixNano(time.Now()) + + // get total stats only + ioCounters, err := s.ioCounters( /*perNetworkInterfaceController=*/ true) + if err != nil { + return consumererror.NewPartialScrapeError(err, networkMetricsLen) + } + + // filter network interfaces by name + ioCounters = s.filterByInterface(ioCounters) + + if len(ioCounters) > 0 { + startIdx := metrics.Len() + metrics.Resize(startIdx + networkMetricsLen) + initializeNetworkPacketsMetric(metrics.At(startIdx+0), networkPacketsDescriptor, startTime, now, ioCounters) + initializeNetworkDroppedPacketsMetric(metrics.At(startIdx+1), networkDroppedPacketsDescriptor, startTime, now, ioCounters) + initializeNetworkErrorsMetric(metrics.At(startIdx+2), networkErrorsDescriptor, startTime, now, ioCounters) + initializeNetworkIOMetric(metrics.At(startIdx+3), networkIODescriptor, startTime, now, ioCounters) + } + + return nil +} + +func initializeNetworkPacketsMetric(metric pdata.Metric, metricDescriptor pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) { + metricDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCountersSlice)) + for idx, ioCounters := range ioCountersSlice { + initializeNetworkDataPoint(idps.At(2*idx+0), startTime, now, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.PacketsSent)) + initializeNetworkDataPoint(idps.At(2*idx+1), startTime, now, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.PacketsRecv)) + } +} + +func initializeNetworkDroppedPacketsMetric(metric pdata.Metric, metricDescriptor pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) { + metricDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCountersSlice)) + for idx, ioCounters := range ioCountersSlice { + initializeNetworkDataPoint(idps.At(2*idx+0), startTime, now, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.Dropout)) + initializeNetworkDataPoint(idps.At(2*idx+1), startTime, now, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.Dropin)) + } +} + +func initializeNetworkErrorsMetric(metric pdata.Metric, metricDescriptor pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) { + metricDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCountersSlice)) + for idx, ioCounters := range ioCountersSlice { + initializeNetworkDataPoint(idps.At(2*idx+0), startTime, now, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.Errout)) + initializeNetworkDataPoint(idps.At(2*idx+1), startTime, now, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.Errin)) + } +} + +func initializeNetworkIOMetric(metric pdata.Metric, metricDescriptor pdata.Metric, startTime, now pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) { + metricDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(ioCountersSlice)) + for idx, ioCounters := range ioCountersSlice { + initializeNetworkDataPoint(idps.At(2*idx+0), startTime, now, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.BytesSent)) + initializeNetworkDataPoint(idps.At(2*idx+1), startTime, now, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.BytesRecv)) + } +} + +func initializeNetworkDataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, interfaceLabel, directionLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(interfaceLabelName, interfaceLabel) + labelsMap.Insert(directionLabelName, directionLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func (s *scraper) scrapeAndAppendNetworkTCPConnectionsMetric(metrics pdata.MetricSlice) error { + now := internal.TimeToUnixNano(time.Now()) + + connections, err := s.connections("tcp") + if err != nil { + return consumererror.NewPartialScrapeError(err, connectionsMetricsLen) + } + + connectionStatusCounts := getTCPConnectionStatusCounts(connections) + + startIdx := metrics.Len() + metrics.Resize(startIdx + connectionsMetricsLen) + initializeNetworkTCPConnectionsMetric(metrics.At(startIdx), now, connectionStatusCounts) + return nil +} + +func getTCPConnectionStatusCounts(connections []net.ConnectionStat) map[string]int64 { + tcpStatuses := make(map[string]int64, len(allTCPStates)) + for _, state := range allTCPStates { + tcpStatuses[state] = 0 + } + + for _, connection := range connections { + tcpStatuses[connection.Status]++ + } + return tcpStatuses +} + +func initializeNetworkTCPConnectionsMetric(metric pdata.Metric, now pdata.TimestampUnixNano, connectionStateCounts map[string]int64) { + networkTCPConnectionsDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(len(connectionStateCounts)) + + i := 0 + for connectionState, count := range connectionStateCounts { + initializeNetworkTCPConnectionsDataPoint(idps.At(i), now, connectionState, count) + i++ + } +} + +func initializeNetworkTCPConnectionsDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, stateLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(stateLabelName, stateLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func (s *scraper) filterByInterface(ioCounters []net.IOCountersStat) []net.IOCountersStat { + if s.includeFS == nil && s.excludeFS == nil { + return ioCounters + } + + filteredIOCounters := make([]net.IOCountersStat, 0, len(ioCounters)) + for _, io := range ioCounters { + if s.includeInterface(io.Name) { + filteredIOCounters = append(filteredIOCounters, io) + } + } + return filteredIOCounters +} + +func (s *scraper) includeInterface(interfaceName string) bool { + return (s.includeFS == nil || s.includeFS.Matches(interfaceName)) && + (s.excludeFS == nil || !s.excludeFS.Matches(interfaceName)) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper_test.go new file mode 100644 index 00000000000..c3498f2ff94 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper_test.go @@ -0,0 +1,171 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkscraper + +import ( + "context" + "errors" + "testing" + + "github.com/shirou/gopsutil/net" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + config Config + bootTimeFunc func() (uint64, error) + ioCountersFunc func(bool) ([]net.IOCountersStat, error) + connectionsFunc func(string) ([]net.ConnectionStat, error) + expectNetworkMetrics bool + expectedStartTime pdata.TimestampUnixNano + newErrRegex string + initializationErr string + expectedErr string + expectedErrCount int + } + + testCases := []testCase{ + { + name: "Standard", + expectNetworkMetrics: true, + }, + { + name: "Validate Start Time", + bootTimeFunc: func() (uint64, error) { return 100, nil }, + expectNetworkMetrics: true, + expectedStartTime: 100 * 1e9, + }, + { + name: "Include Filter that matches nothing", + config: Config{Include: MatchConfig{filterset.Config{MatchType: "strict"}, []string{"@*^#&*$^#)"}}}, + expectNetworkMetrics: false, + }, + { + name: "Invalid Include Filter", + config: Config{Include: MatchConfig{Interfaces: []string{"test"}}}, + newErrRegex: "^error creating network interface include filters:", + }, + { + name: "Invalid Exclude Filter", + config: Config{Exclude: MatchConfig{Interfaces: []string{"test"}}}, + newErrRegex: "^error creating network interface exclude filters:", + }, + { + name: "Boot Time Error", + bootTimeFunc: func() (uint64, error) { return 0, errors.New("err1") }, + initializationErr: "err1", + }, + { + name: "IOCounters Error", + ioCountersFunc: func(bool) ([]net.IOCountersStat, error) { return nil, errors.New("err2") }, + expectedErr: "err2", + expectedErrCount: networkMetricsLen, + }, + { + name: "Connections Error", + connectionsFunc: func(string) ([]net.ConnectionStat, error) { return nil, errors.New("err3") }, + expectedErr: "err3", + expectedErrCount: connectionsMetricsLen, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper, err := newNetworkScraper(context.Background(), &test.config) + if test.newErrRegex != "" { + require.Error(t, err) + require.Regexp(t, test.newErrRegex, err) + return + } + require.NoError(t, err, "Failed to create network scraper: %v", err) + + if test.bootTimeFunc != nil { + scraper.bootTime = test.bootTimeFunc + } + if test.ioCountersFunc != nil { + scraper.ioCounters = test.ioCountersFunc + } + if test.connectionsFunc != nil { + scraper.connections = test.connectionsFunc + } + + err = scraper.start(context.Background(), componenttest.NewNopHost()) + if test.initializationErr != "" { + assert.EqualError(t, err, test.initializationErr) + return + } + require.NoError(t, err, "Failed to initialize network scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, test.expectedErrCount, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + expectedMetricCount := 1 + if test.expectNetworkMetrics { + expectedMetricCount += 4 + } + assert.Equal(t, expectedMetricCount, metrics.Len()) + + idx := 0 + if test.expectNetworkMetrics { + assertNetworkIOMetricValid(t, metrics.At(idx+0), networkPacketsDescriptor, test.expectedStartTime) + assertNetworkIOMetricValid(t, metrics.At(idx+1), networkDroppedPacketsDescriptor, test.expectedStartTime) + assertNetworkIOMetricValid(t, metrics.At(idx+2), networkErrorsDescriptor, test.expectedStartTime) + assertNetworkIOMetricValid(t, metrics.At(idx+3), networkIODescriptor, test.expectedStartTime) + internal.AssertSameTimeStampForMetrics(t, metrics, 0, 4) + idx += 4 + } + + assertNetworkTCPConnectionsMetricValid(t, metrics.At(idx+0)) + internal.AssertSameTimeStampForMetrics(t, metrics, idx, idx+1) + }) + } +} + +func assertNetworkIOMetricValid(t *testing.T, metric pdata.Metric, descriptor pdata.Metric, startTime pdata.TimestampUnixNano) { + internal.AssertDescriptorEqual(t, descriptor, metric) + if startTime != 0 { + internal.AssertIntSumMetricStartTimeEquals(t, metric, startTime) + } + assert.GreaterOrEqual(t, metric.IntSum().DataPoints().Len(), 2) + internal.AssertIntSumMetricLabelExists(t, metric, 0, interfaceLabelName) + internal.AssertIntSumMetricLabelHasValue(t, metric, 0, directionLabelName, transmitDirectionLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, metric, 1, directionLabelName, receiveDirectionLabelValue) +} + +func assertNetworkTCPConnectionsMetricValid(t *testing.T, metric pdata.Metric) { + internal.AssertDescriptorEqual(t, networkTCPConnectionsDescriptor, metric) + internal.AssertIntSumMetricLabelExists(t, metric, 0, stateLabelName) + assert.Equal(t, 12, metric.IntSum().DataPoints().Len()) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_windows.go new file mode 100644 index 00000000000..ccd995895ee --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_windows.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package networkscraper + +var allTCPStates = []string{ + "CLOSE_WAIT", + "CLOSED", + "CLOSING", + "DELETE", + "ESTABLISHED", + "FIN_WAIT_1", + "FIN_WAIT_2", + "LAST_ACK", + "LISTEN", + "SYN_SENT", + "SYN_RECEIVED", + "TIME_WAIT", +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go new file mode 100644 index 00000000000..4245727c8b1 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processesscraper + +import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + +// Config relating to Processes Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go new file mode 100644 index 00000000000..06f843f75b1 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processesscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Processes scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "processes" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s := newProcessesScraper(ctx, cfg) + + ms := scraperhelper.NewMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go new file mode 100644 index 00000000000..cb74af9d2b2 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processesscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go new file mode 100644 index 00000000000..813bd2749b9 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processesscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// descriptors + +var processesRunningDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.processes.running") + metric.SetDescription("Total number of running processes.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var processesBlockedDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.processes.blocked") + metric.SetDescription("Total number of blocked processes.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go new file mode 100644 index 00000000000..23714a77b92 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processesscraper + +import ( + "context" + + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/load" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/pdata" +) + +const metricsLen = systemSpecificMetricsLen + +// scraper for Processes Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + + // for mocking gopsutil load.Misc + misc getMiscStats +} + +type getMiscStats func() (*load.MiscStat, error) + +// newProcessesScraper creates a set of Processes related metrics +func newProcessesScraper(_ context.Context, cfg *Config) *scraper { + return &scraper{config: cfg, misc: load.Misc} +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := host.BootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime) + return nil +} + +func (s *scraper) scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + err := appendSystemSpecificProcessesMetrics(metrics, 0, s.misc) + return metrics, err +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go new file mode 100644 index 00000000000..33f07cdc12b --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux,!darwin,!freebsd,!openbsd + +package processesscraper + +import "go.opentelemetry.io/collector/consumer/pdata" + +const systemSpecificMetricsLen = 2 + +func appendSystemSpecificProcessesMetrics(metrics pdata.MetricSlice, startIndex int, miscFunc getMiscStats) error { + return nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go new file mode 100644 index 00000000000..5d2ff7b1a9d --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processesscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/shirou/gopsutil/load" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +var systemSpecificMetrics = map[string][]pdata.Metric{ + "linux": {processesRunningDescriptor, processesBlockedDescriptor}, + "darwin": {processesRunningDescriptor, processesBlockedDescriptor}, + "freebsd": {processesRunningDescriptor, processesBlockedDescriptor}, + "openbsd": {processesRunningDescriptor, processesBlockedDescriptor}, +} + +func TestScrape(t *testing.T) { + type testCase struct { + name string + miscFunc func() (*load.MiscStat, error) + expectedErr string + } + + testCases := []testCase{ + { + name: "Standard", + }, + { + name: "Error", + miscFunc: func() (*load.MiscStat, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + }, + } + + expectedMetrics := systemSpecificMetrics[runtime.GOOS] + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newProcessesScraper(context.Background(), &Config{}) + if test.miscFunc != nil { + scraper.misc = test.miscFunc + } + + err := scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize processes scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + if len(expectedMetrics) > 0 && test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, metricsLen, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + assert.Equal(t, len(expectedMetrics), metrics.Len()) + for i, expectedMetricDescriptor := range expectedMetrics { + assertProcessesMetricValid(t, metrics.At(i), expectedMetricDescriptor) + } + + internal.AssertSameTimeStampForAllMetrics(t, metrics) + }) + } +} + +func assertProcessesMetricValid(t *testing.T, metric pdata.Metric, descriptor pdata.Metric) { + internal.AssertDescriptorEqual(t, descriptor, metric) + assert.Equal(t, metric.IntSum().DataPoints().Len(), 1) + assert.Equal(t, metric.IntSum().DataPoints().At(0).LabelsMap().Len(), 0) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_unix.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_unix.go new file mode 100644 index 00000000000..82794399630 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_unix.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux darwin freebsd openbsd + +package processesscraper + +import ( + "time" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +const systemSpecificMetricsLen = 2 + +func appendSystemSpecificProcessesMetrics(metrics pdata.MetricSlice, startIndex int, miscFunc getMiscStats) error { + now := internal.TimeToUnixNano(time.Now()) + misc, err := miscFunc() + if err != nil { + return consumererror.NewPartialScrapeError(err, systemSpecificMetricsLen) + } + + metrics.Resize(startIndex + systemSpecificMetricsLen) + initializeProcessesMetric(metrics.At(startIndex+0), processesRunningDescriptor, now, int64(misc.ProcsRunning)) + initializeProcessesMetric(metrics.At(startIndex+1), processesBlockedDescriptor, now, int64(misc.ProcsBlocked)) + return nil +} + +func initializeProcessesMetric(metric pdata.Metric, descriptor pdata.Metric, now pdata.TimestampUnixNano, value int64) { + descriptor.CopyTo(metric) + + ddps := metric.IntSum().DataPoints() + ddps.Resize(1) + ddps.At(0).SetTimestamp(now) + ddps.At(0).SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go new file mode 100644 index 00000000000..d5a3d913074 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +// Config relating to Process Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // Include specifies a filter on the process names that should be included from the generated metrics. + // Exclude specifies a filter on the process names that should be excluded from the generated metrics. + // If neither `include` or `exclude` are set, process metrics will be generated for all processes. + Include MatchConfig `mapstructure:"include"` + Exclude MatchConfig `mapstructure:"exclude"` +} + +type MatchConfig struct { + filterset.Config `mapstructure:",squash"` + + Names []string `mapstructure:"names"` +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory.go new file mode 100644 index 00000000000..d9ef18ad599 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "context" + "errors" + "runtime" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Process scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "process" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateResourceMetricsScraper creates a resource scraper based on provided config. +func (f *Factory) CreateResourceMetricsScraper( + _ context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.ResourceMetricsScraper, error) { + if runtime.GOOS != "linux" && runtime.GOOS != "windows" { + return nil, errors.New("process scraper only available on Linux or Windows") + } + + cfg := config.(*Config) + s, err := newProcessScraper(cfg) + if err != nil { + return nil, err + } + + ms := scraperhelper.NewResourceMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory_test.go new file mode 100644 index 00000000000..d16165e2379 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/factory_test.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "context" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateResourceMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateResourceMetricsScraper(context.Background(), zap.NewNop(), cfg) + + if runtime.GOOS == "linux" || runtime.GOOS == "windows" { + assert.NoError(t, err) + assert.NotNil(t, scraper) + } else { + assert.Error(t, err) + assert.Nil(t, scraper) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process.go new file mode 100644 index 00000000000..d432a7611fc --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process.go @@ -0,0 +1,134 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "strings" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/process" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +// processMetadata stores process related metadata along +// with the process handle, and provides a function to +// initialize a pdata.Resource with the metadata + +type processMetadata struct { + pid int32 + executable *executableMetadata + command *commandMetadata + username string + handle processHandle +} + +type executableMetadata struct { + name string + path string +} + +type commandMetadata struct { + command string + commandLine string + commandLineSlice []string +} + +func (m *processMetadata) initializeResource(resource pdata.Resource) { + attr := resource.Attributes() + attr.InitEmptyWithCapacity(6) + m.insertPid(attr) + m.insertExecutable(attr) + m.insertCommand(attr) + m.insertUsername(attr) +} + +func (m *processMetadata) insertPid(attr pdata.AttributeMap) { + attr.InsertInt(conventions.AttributeProcessID, int64(m.pid)) +} + +func (m *processMetadata) insertExecutable(attr pdata.AttributeMap) { + attr.InsertString(conventions.AttributeProcessExecutableName, m.executable.name) + attr.InsertString(conventions.AttributeProcessExecutablePath, m.executable.path) +} + +func (m *processMetadata) insertCommand(attr pdata.AttributeMap) { + if m.command == nil { + return + } + + attr.InsertString(conventions.AttributeProcessCommand, m.command.command) + if m.command.commandLineSlice != nil { + // TODO insert slice here once this is supported by the data model + // (see https://github.com/open-telemetry/opentelemetry-collector/pull/1142) + attr.InsertString(conventions.AttributeProcessCommandLine, strings.Join(m.command.commandLineSlice, " ")) + } else { + attr.InsertString(conventions.AttributeProcessCommandLine, m.command.commandLine) + } +} + +func (m *processMetadata) insertUsername(attr pdata.AttributeMap) { + if m.username == "" { + return + } + + attr.InsertString(conventions.AttributeProcessOwner, m.username) +} + +// processHandles provides a wrapper around []*process.Process +// to support testing + +type processHandles interface { + Pid(index int) int32 + At(index int) processHandle + Len() int +} + +type processHandle interface { + Name() (string, error) + Exe() (string, error) + Username() (string, error) + Cmdline() (string, error) + CmdlineSlice() ([]string, error) + Times() (*cpu.TimesStat, error) + MemoryInfo() (*process.MemoryInfoStat, error) + IOCounters() (*process.IOCountersStat, error) +} + +type gopsProcessHandles struct { + handles []*process.Process +} + +func (p *gopsProcessHandles) Pid(index int) int32 { + return p.handles[index].Pid +} + +func (p *gopsProcessHandles) At(index int) processHandle { + return p.handles[index] +} + +func (p *gopsProcessHandles) Len() int { + return len(p.handles) +} + +func getProcessHandlesInternal() (processHandles, error) { + processes, err := process.Processes() + if err != nil { + return nil, err + } + + return &gopsProcessHandles{handles: processes}, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_metadata.go new file mode 100644 index 00000000000..449f05e2a56 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_metadata.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// labels + +const ( + directionLabelName = "direction" + stateLabelName = "state" +) + +// direction label values + +const ( + readDirectionLabelValue = "read" + writeDirectionLabelValue = "write" +) + +// state label values + +const ( + userStateLabelValue = "user" + systemStateLabelValue = "system" + waitStateLabelValue = "wait" +) + +// descriptors + +var cpuTimeDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("process.cpu.time") + metric.SetDescription("Total CPU seconds broken down by different states.") + metric.SetUnit("s") + metric.SetDataType(pdata.MetricDataTypeDoubleSum) + sum := metric.DoubleSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var physicalMemoryUsageDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("process.memory.physical_usage") + metric.SetDescription("The amount of physical memory in use.") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var virtualMemoryUsageDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("process.memory.virtual_usage") + metric.SetDescription("Virtual memory size.") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var diskIODescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("process.disk.io") + metric.SetDescription("Disk bytes transferred.") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper.go new file mode 100644 index 00000000000..8588b6c75c1 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper.go @@ -0,0 +1,253 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "context" + "fmt" + "time" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/process" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +const ( + cpuMetricsLen = 1 + memoryMetricsLen = 2 + diskMetricsLen = 1 + + metricsLen = cpuMetricsLen + memoryMetricsLen + diskMetricsLen +) + +// scraper for Process Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + includeFS filterset.FilterSet + excludeFS filterset.FilterSet + + // for mocking + bootTime func() (uint64, error) + getProcessHandles func() (processHandles, error) +} + +// newProcessScraper creates a Process Scraper +func newProcessScraper(cfg *Config) (*scraper, error) { + scraper := &scraper{config: cfg, bootTime: host.BootTime, getProcessHandles: getProcessHandlesInternal} + + var err error + + if len(cfg.Include.Names) > 0 { + scraper.includeFS, err = filterset.CreateFilterSet(cfg.Include.Names, &cfg.Include.Config) + if err != nil { + return nil, fmt.Errorf("error creating process include filters: %w", err) + } + } + + if len(cfg.Exclude.Names) > 0 { + scraper.excludeFS, err = filterset.CreateFilterSet(cfg.Exclude.Names, &cfg.Exclude.Config) + if err != nil { + return nil, fmt.Errorf("error creating process exclude filters: %w", err) + } + } + + return scraper, nil +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + return nil +} + +func (s *scraper) scrape(_ context.Context) (pdata.ResourceMetricsSlice, error) { + rms := pdata.NewResourceMetricsSlice() + + var errs []error + + metadata, err := s.getProcessMetadata() + if err != nil { + if !consumererror.IsPartialScrapeError(err) { + return rms, err + } + + errs = append(errs, err) + } + + rms.Resize(len(metadata)) + for i, md := range metadata { + rm := rms.At(i) + md.initializeResource(rm.Resource()) + + ilms := rm.InstrumentationLibraryMetrics() + ilms.Resize(1) + metrics := ilms.At(0).Metrics() + + now := internal.TimeToUnixNano(time.Now()) + + if err = scrapeAndAppendCPUTimeMetric(metrics, s.startTime, now, md.handle); err != nil { + errs = append(errs, consumererror.NewPartialScrapeError(fmt.Errorf("error reading cpu times for process %q (pid %v): %w", md.executable.name, md.pid, err), cpuMetricsLen)) + } + + if err = scrapeAndAppendMemoryUsageMetrics(metrics, now, md.handle); err != nil { + errs = append(errs, consumererror.NewPartialScrapeError(fmt.Errorf("error reading memory info for process %q (pid %v): %w", md.executable.name, md.pid, err), memoryMetricsLen)) + } + + if err = scrapeAndAppendDiskIOMetric(metrics, s.startTime, now, md.handle); err != nil { + errs = append(errs, consumererror.NewPartialScrapeError(fmt.Errorf("error reading disk usage for process %q (pid %v): %w", md.executable.name, md.pid, err), diskMetricsLen)) + } + } + + return rms, scraperhelper.CombineScrapeErrors(errs) +} + +// getProcessMetadata returns a slice of processMetadata, including handles, +// for all currently running processes. If errors occur obtaining information +// for some processes, an error will be returned, but any processes that were +// successfully obtained will still be returned. +func (s *scraper) getProcessMetadata() ([]*processMetadata, error) { + handles, err := s.getProcessHandles() + if err != nil { + return nil, err + } + + var errs []error + metadata := make([]*processMetadata, 0, handles.Len()) + for i := 0; i < handles.Len(); i++ { + pid := handles.Pid(i) + handle := handles.At(i) + + executable, err := getProcessExecutable(handle) + if err != nil { + errs = append(errs, consumererror.NewPartialScrapeError(fmt.Errorf("error reading process name for pid %v: %w", pid, err), 1)) + continue + } + + // filter processes by name + if (s.includeFS != nil && !s.includeFS.Matches(executable.name)) || + (s.excludeFS != nil && s.excludeFS.Matches(executable.name)) { + continue + } + + command, err := getProcessCommand(handle) + if err != nil { + errs = append(errs, consumererror.NewPartialScrapeError(fmt.Errorf("error reading command for process %q (pid %v): %w", executable.name, pid, err), 0)) + } + + username, err := handle.Username() + if err != nil { + errs = append(errs, consumererror.NewPartialScrapeError(fmt.Errorf("error reading username for process %q (pid %v): %w", executable.name, pid, err), 0)) + } + + md := &processMetadata{ + pid: pid, + executable: executable, + command: command, + username: username, + handle: handle, + } + + metadata = append(metadata, md) + } + + return metadata, scraperhelper.CombineScrapeErrors(errs) +} + +func scrapeAndAppendCPUTimeMetric(metrics pdata.MetricSlice, startTime, now pdata.TimestampUnixNano, handle processHandle) error { + times, err := handle.Times() + if err != nil { + return err + } + + startIdx := metrics.Len() + metrics.Resize(startIdx + cpuMetricsLen) + initializeCPUTimeMetric(metrics.At(startIdx), startTime, now, times) + return nil +} + +func initializeCPUTimeMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, times *cpu.TimesStat) { + cpuTimeDescriptor.CopyTo(metric) + + ddps := metric.DoubleSum().DataPoints() + ddps.Resize(cpuStatesLen) + appendCPUTimeStateDataPoints(ddps, startTime, now, times) +} + +func scrapeAndAppendMemoryUsageMetrics(metrics pdata.MetricSlice, now pdata.TimestampUnixNano, handle processHandle) error { + mem, err := handle.MemoryInfo() + if err != nil { + return err + } + + startIdx := metrics.Len() + metrics.Resize(startIdx + memoryMetricsLen) + initializeMemoryUsageMetric(metrics.At(startIdx+0), physicalMemoryUsageDescriptor, now, int64(mem.RSS)) + initializeMemoryUsageMetric(metrics.At(startIdx+1), virtualMemoryUsageDescriptor, now, int64(mem.VMS)) + return nil +} + +func initializeMemoryUsageMetric(metric pdata.Metric, descriptor pdata.Metric, now pdata.TimestampUnixNano, usage int64) { + descriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(1) + initializeMemoryUsageDataPoint(idps.At(0), now, usage) +} + +func initializeMemoryUsageDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, usage int64) { + dataPoint.SetTimestamp(now) + dataPoint.SetValue(usage) +} + +func scrapeAndAppendDiskIOMetric(metrics pdata.MetricSlice, startTime, now pdata.TimestampUnixNano, handle processHandle) error { + io, err := handle.IOCounters() + if err != nil { + return err + } + + startIdx := metrics.Len() + metrics.Resize(startIdx + diskMetricsLen) + initializeDiskIOMetric(metrics.At(startIdx), startTime, now, io) + return nil +} + +func initializeDiskIOMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, io *process.IOCountersStat) { + diskIODescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2) + initializeDiskIODataPoint(idps.At(0), startTime, now, int64(io.ReadBytes), readDirectionLabelValue) + initializeDiskIODataPoint(idps.At(1), startTime, now, int64(io.WriteBytes), writeDirectionLabelValue) +} + +func initializeDiskIODataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, value int64, directionLabel string) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(directionLabelName, directionLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_linux.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_linux.go new file mode 100644 index 00000000000..62dc40cde23 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_linux.go @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package processscraper + +import ( + "github.com/shirou/gopsutil/cpu" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const cpuStatesLen = 3 + +func appendCPUTimeStateDataPoints(ddps pdata.DoubleDataPointSlice, startTime, now pdata.TimestampUnixNano, cpuTime *cpu.TimesStat) { + initializeCPUTimeDataPoint(ddps.At(0), startTime, now, cpuTime.User, userStateLabelValue) + initializeCPUTimeDataPoint(ddps.At(1), startTime, now, cpuTime.System, systemStateLabelValue) + initializeCPUTimeDataPoint(ddps.At(2), startTime, now, cpuTime.Iowait, waitStateLabelValue) +} + +func initializeCPUTimeDataPoint(dataPoint pdata.DoubleDataPoint, startTime, now pdata.TimestampUnixNano, value float64, stateLabel string) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(stateLabelName, stateLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func getProcessExecutable(proc processHandle) (*executableMetadata, error) { + name, err := proc.Name() + if err != nil { + return nil, err + } + + exe, err := proc.Exe() + if err != nil { + return nil, err + } + + executable := &executableMetadata{name: name, path: exe} + return executable, nil +} + +func getProcessCommand(proc processHandle) (*commandMetadata, error) { + cmdline, err := proc.CmdlineSlice() + if err != nil { + return nil, err + } + + var cmd string + if len(cmdline) > 0 { + cmd = cmdline[0] + } + + command := &commandMetadata{command: cmd, commandLineSlice: cmdline} + return command, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_others.go new file mode 100644 index 00000000000..b3056d8a933 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_others.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux,!windows + +package processscraper + +import ( + "github.com/shirou/gopsutil/cpu" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const cpuStatesLen = 0 + +func appendCPUTimeStateDataPoints(ddps pdata.DoubleDataPointSlice, startTime, now pdata.TimestampUnixNano, cpuTime *cpu.TimesStat) { +} + +func getProcessExecutable(processHandle) (*executableMetadata, error) { + return nil, nil +} + +func getProcessCommand(processHandle) (*commandMetadata, error) { + return nil, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_test.go new file mode 100644 index 00000000000..bb836907ec2 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_test.go @@ -0,0 +1,485 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package processscraper + +import ( + "context" + "errors" + "fmt" + "runtime" + "testing" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/process" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/processor/filterset" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/translator/conventions" +) + +func skipTestOnUnsupportedOS(t *testing.T) { + if runtime.GOOS != "linux" && runtime.GOOS != "windows" { + t.Skipf("skipping test on %v", runtime.GOOS) + } +} + +func TestScrape(t *testing.T) { + skipTestOnUnsupportedOS(t) + + const bootTime = 100 + const expectedStartTime = 100 * 1e9 + + scraper, err := newProcessScraper(&Config{}) + scraper.bootTime = func() (uint64, error) { return bootTime, nil } + require.NoError(t, err, "Failed to create process scraper: %v", err) + err = scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize process scraper: %v", err) + + resourceMetrics, err := scraper.scrape(context.Background()) + + // may receive some partial errors as a result of attempting to: + // a) read native system processes on Windows (e.g. Registry process) + // b) read info on processes that have just terminated + // + // so validate that we have less processes that were failed to be scraped + // than processes that were successfully scraped & some valid data is + // returned + if err != nil { + require.True(t, consumererror.IsPartialScrapeError(err)) + noProcessesScraped := resourceMetrics.Len() + noProcessesErrored := err.(consumererror.PartialScrapeError).Failed + require.Lessf(t, noProcessesErrored, noProcessesScraped, "Failed to scrape metrics - more processes failed to be scraped than were successfully scraped: %v", err) + } + + require.Greater(t, resourceMetrics.Len(), 1) + assertProcessResourceAttributesExist(t, resourceMetrics) + assertCPUTimeMetricValid(t, resourceMetrics, expectedStartTime) + assertMemoryUsageMetricValid(t, physicalMemoryUsageDescriptor, resourceMetrics) + assertMemoryUsageMetricValid(t, virtualMemoryUsageDescriptor, resourceMetrics) + assertDiskIOMetricValid(t, resourceMetrics, expectedStartTime) + assertSameTimeStampForAllMetricsWithinResource(t, resourceMetrics) +} + +func assertProcessResourceAttributesExist(t *testing.T, resourceMetrics pdata.ResourceMetricsSlice) { + for i := 0; i < resourceMetrics.Len(); i++ { + attr := resourceMetrics.At(0).Resource().Attributes() + internal.AssertContainsAttribute(t, attr, conventions.AttributeProcessID) + internal.AssertContainsAttribute(t, attr, conventions.AttributeProcessExecutableName) + internal.AssertContainsAttribute(t, attr, conventions.AttributeProcessExecutablePath) + internal.AssertContainsAttribute(t, attr, conventions.AttributeProcessCommand) + internal.AssertContainsAttribute(t, attr, conventions.AttributeProcessCommandLine) + internal.AssertContainsAttribute(t, attr, conventions.AttributeProcessOwner) + } +} + +func assertCPUTimeMetricValid(t *testing.T, resourceMetrics pdata.ResourceMetricsSlice, startTime pdata.TimestampUnixNano) { + cpuTimeMetric := getMetric(t, cpuTimeDescriptor, resourceMetrics) + internal.AssertDescriptorEqual(t, cpuTimeDescriptor, cpuTimeMetric) + if startTime != 0 { + internal.AssertDoubleSumMetricStartTimeEquals(t, cpuTimeMetric, startTime) + } + internal.AssertDoubleSumMetricLabelHasValue(t, cpuTimeMetric, 0, stateLabelName, userStateLabelValue) + internal.AssertDoubleSumMetricLabelHasValue(t, cpuTimeMetric, 1, stateLabelName, systemStateLabelValue) + if runtime.GOOS == "linux" { + internal.AssertDoubleSumMetricLabelHasValue(t, cpuTimeMetric, 2, stateLabelName, waitStateLabelValue) + } +} + +func assertMemoryUsageMetricValid(t *testing.T, descriptor pdata.Metric, resourceMetrics pdata.ResourceMetricsSlice) { + memoryUsageMetric := getMetric(t, descriptor, resourceMetrics) + internal.AssertDescriptorEqual(t, descriptor, memoryUsageMetric) +} + +func assertDiskIOMetricValid(t *testing.T, resourceMetrics pdata.ResourceMetricsSlice, startTime pdata.TimestampUnixNano) { + diskIOMetric := getMetric(t, diskIODescriptor, resourceMetrics) + internal.AssertDescriptorEqual(t, diskIODescriptor, diskIOMetric) + if startTime != 0 { + internal.AssertIntSumMetricStartTimeEquals(t, diskIOMetric, startTime) + } + internal.AssertIntSumMetricLabelHasValue(t, diskIOMetric, 0, directionLabelName, readDirectionLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, diskIOMetric, 1, directionLabelName, writeDirectionLabelValue) +} + +func assertSameTimeStampForAllMetricsWithinResource(t *testing.T, resourceMetrics pdata.ResourceMetricsSlice) { + for i := 0; i < resourceMetrics.Len(); i++ { + ilms := resourceMetrics.At(i).InstrumentationLibraryMetrics() + for j := 0; j < ilms.Len(); j++ { + internal.AssertSameTimeStampForAllMetrics(t, ilms.At(j).Metrics()) + } + } +} + +func getMetric(t *testing.T, descriptor pdata.Metric, rms pdata.ResourceMetricsSlice) pdata.Metric { + for i := 0; i < rms.Len(); i++ { + metrics := getMetricSlice(t, rms.At(i)) + for j := 0; j < metrics.Len(); j++ { + metric := metrics.At(j) + if metric.Name() == descriptor.Name() { + return metric + } + } + } + + require.Fail(t, fmt.Sprintf("no metric with name %s was returned", descriptor.Name())) + return pdata.NewMetric() +} + +func getMetricSlice(t *testing.T, rm pdata.ResourceMetrics) pdata.MetricSlice { + ilms := rm.InstrumentationLibraryMetrics() + require.Equal(t, 1, ilms.Len()) + return ilms.At(0).Metrics() +} + +func TestScrapeMetrics_NewError(t *testing.T) { + skipTestOnUnsupportedOS(t) + + _, err := newProcessScraper(&Config{Include: MatchConfig{Names: []string{"test"}}}) + require.Error(t, err) + require.Regexp(t, "^error creating process include filters:", err.Error()) + + _, err = newProcessScraper(&Config{Exclude: MatchConfig{Names: []string{"test"}}}) + require.Error(t, err) + require.Regexp(t, "^error creating process exclude filters:", err.Error()) +} + +func TestScrapeMetrics_GetProcessesError(t *testing.T) { + skipTestOnUnsupportedOS(t) + + scraper, err := newProcessScraper(&Config{}) + require.NoError(t, err, "Failed to create process scraper: %v", err) + + scraper.getProcessHandles = func() (processHandles, error) { return nil, errors.New("err1") } + + err = scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize process scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + assert.EqualError(t, err, "err1") + assert.Equal(t, 0, metrics.Len()) + assert.False(t, consumererror.IsPartialScrapeError(err)) +} + +type processHandlesMock struct { + handles []*processHandleMock +} + +func (p *processHandlesMock) Pid(index int) int32 { + return 1 +} + +func (p *processHandlesMock) At(index int) processHandle { + return p.handles[index] +} + +func (p *processHandlesMock) Len() int { + return len(p.handles) +} + +type processHandleMock struct { + mock.Mock +} + +func (p *processHandleMock) Name() (ret string, err error) { + args := p.MethodCalled("Name") + return args.String(0), args.Error(1) +} + +func (p *processHandleMock) Exe() (string, error) { + args := p.MethodCalled("Exe") + return args.String(0), args.Error(1) +} + +func (p *processHandleMock) Username() (string, error) { + args := p.MethodCalled("Username") + return args.String(0), args.Error(1) +} + +func (p *processHandleMock) Cmdline() (string, error) { + args := p.MethodCalled("Cmdline") + return args.String(0), args.Error(1) +} + +func (p *processHandleMock) CmdlineSlice() ([]string, error) { + args := p.MethodCalled("CmdlineSlice") + return args.Get(0).([]string), args.Error(1) +} + +func (p *processHandleMock) Times() (*cpu.TimesStat, error) { + args := p.MethodCalled("Times") + return args.Get(0).(*cpu.TimesStat), args.Error(1) +} + +func (p *processHandleMock) MemoryInfo() (*process.MemoryInfoStat, error) { + args := p.MethodCalled("MemoryInfo") + return args.Get(0).(*process.MemoryInfoStat), args.Error(1) +} + +func (p *processHandleMock) IOCounters() (*process.IOCountersStat, error) { + args := p.MethodCalled("IOCounters") + return args.Get(0).(*process.IOCountersStat), args.Error(1) +} + +func newDefaultHandleMock() *processHandleMock { + handleMock := &processHandleMock{} + handleMock.On("Username").Return("username", nil) + handleMock.On("Cmdline").Return("cmdline", nil) + handleMock.On("CmdlineSlice").Return([]string{"cmdline"}, nil) + handleMock.On("Times").Return(&cpu.TimesStat{}, nil) + handleMock.On("MemoryInfo").Return(&process.MemoryInfoStat{}, nil) + handleMock.On("IOCounters").Return(&process.IOCountersStat{}, nil) + return handleMock +} + +func TestScrapeMetrics_Filtered(t *testing.T) { + skipTestOnUnsupportedOS(t) + + type testCase struct { + name string + names []string + include []string + exclude []string + expectedNames []string + } + + testCases := []testCase{ + { + name: "No Filter", + names: []string{"test1", "test2"}, + include: []string{"test*"}, + expectedNames: []string{"test1", "test2"}, + }, + { + name: "Include All", + names: []string{"test1", "test2"}, + include: []string{"test*"}, + expectedNames: []string{"test1", "test2"}, + }, + { + name: "Include One", + names: []string{"test1", "test2"}, + include: []string{"test1"}, + expectedNames: []string{"test1"}, + }, + { + name: "Exclude All", + names: []string{"test1", "test2"}, + exclude: []string{"test*"}, + expectedNames: []string{}, + }, + { + name: "Include & Exclude", + names: []string{"test1", "test2"}, + include: []string{"test*"}, + exclude: []string{"test2"}, + expectedNames: []string{"test1"}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + config := &Config{} + + if len(test.include) > 0 { + config.Include = MatchConfig{ + Names: test.include, + Config: filterset.Config{MatchType: filterset.Regexp}, + } + } + if len(test.exclude) > 0 { + config.Exclude = MatchConfig{ + Names: test.exclude, + Config: filterset.Config{MatchType: filterset.Regexp}, + } + } + + scraper, err := newProcessScraper(config) + require.NoError(t, err, "Failed to create process scraper: %v", err) + err = scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize process scraper: %v", err) + + handles := make([]*processHandleMock, 0, len(test.names)) + for _, name := range test.names { + handleMock := newDefaultHandleMock() + handleMock.On("Name").Return(name, nil) + handleMock.On("Exe").Return(name, nil) + handles = append(handles, handleMock) + } + + scraper.getProcessHandles = func() (processHandles, error) { + return &processHandlesMock{handles: handles}, nil + } + + resourceMetrics, err := scraper.scrape(context.Background()) + require.NoError(t, err) + + assert.Equal(t, len(test.expectedNames), resourceMetrics.Len()) + for i, expectedName := range test.expectedNames { + rm := resourceMetrics.At(i) + name, _ := rm.Resource().Attributes().Get(conventions.AttributeProcessExecutableName) + assert.Equal(t, expectedName, name.StringVal()) + } + }) + } +} + +func TestScrapeMetrics_ProcessErrors(t *testing.T) { + skipTestOnUnsupportedOS(t) + + type testCase struct { + name string + osFilter string + nameError error + exeError error + usernameError error + cmdlineError error + timesError error + memoryInfoError error + ioCountersError error + expectedError string + } + + testCases := []testCase{ + { + name: "Name Error", + osFilter: "windows", + nameError: errors.New("err1"), + expectedError: `error reading process name for pid 1: err1`, + }, + { + name: "Exe Error", + exeError: errors.New("err1"), + expectedError: `error reading process name for pid 1: err1`, + }, + { + name: "Cmdline Error", + cmdlineError: errors.New("err2"), + expectedError: `error reading command for process "test" (pid 1): err2`, + }, + { + name: "Username Error", + usernameError: errors.New("err3"), + expectedError: `error reading username for process "test" (pid 1): err3`, + }, + { + name: "Times Error", + timesError: errors.New("err4"), + expectedError: `error reading cpu times for process "test" (pid 1): err4`, + }, + { + name: "Memory Info Error", + memoryInfoError: errors.New("err5"), + expectedError: `error reading memory info for process "test" (pid 1): err5`, + }, + { + name: "IO Counters Error", + ioCountersError: errors.New("err6"), + expectedError: `error reading disk usage for process "test" (pid 1): err6`, + }, + { + name: "Multiple Errors", + cmdlineError: errors.New("err2"), + usernameError: errors.New("err3"), + timesError: errors.New("err4"), + memoryInfoError: errors.New("err5"), + ioCountersError: errors.New("err6"), + expectedError: `[[error reading command for process "test" (pid 1): err2; ` + + `error reading username for process "test" (pid 1): err3]; ` + + `error reading cpu times for process "test" (pid 1): err4; ` + + `error reading memory info for process "test" (pid 1): err5; ` + + `error reading disk usage for process "test" (pid 1): err6]`, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + if test.osFilter == runtime.GOOS { + t.Skipf("skipping test %v on %v", test.name, runtime.GOOS) + } + + scraper, err := newProcessScraper(&Config{}) + require.NoError(t, err, "Failed to create process scraper: %v", err) + err = scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize process scraper: %v", err) + + username := "username" + if test.usernameError != nil { + username = "" + } + + handleMock := &processHandleMock{} + handleMock.On("Name").Return("test", test.nameError) + handleMock.On("Exe").Return("test", test.exeError) + handleMock.On("Username").Return(username, test.usernameError) + handleMock.On("Cmdline").Return("cmdline", test.cmdlineError) + handleMock.On("CmdlineSlice").Return([]string{"cmdline"}, test.cmdlineError) + handleMock.On("Times").Return(&cpu.TimesStat{}, test.timesError) + handleMock.On("MemoryInfo").Return(&process.MemoryInfoStat{}, test.memoryInfoError) + handleMock.On("IOCounters").Return(&process.IOCountersStat{}, test.ioCountersError) + + scraper.getProcessHandles = func() (processHandles, error) { + return &processHandlesMock{handles: []*processHandleMock{handleMock}}, nil + } + + resourceMetrics, err := scraper.scrape(context.Background()) + + md := pdata.NewMetrics() + resourceMetrics.MoveAndAppendTo(md.ResourceMetrics()) + expectedResourceMetricsLen, expectedMetricsLen := getExpectedLengthOfReturnedMetrics(test.nameError, test.exeError, test.timesError, test.memoryInfoError, test.ioCountersError) + assert.Equal(t, expectedResourceMetricsLen, md.ResourceMetrics().Len()) + assert.Equal(t, expectedMetricsLen, md.MetricCount()) + + assert.EqualError(t, err, test.expectedError) + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + expectedFailures := getExpectedScrapeFailures(test.nameError, test.exeError, test.timesError, test.memoryInfoError, test.ioCountersError) + assert.Equal(t, expectedFailures, err.(consumererror.PartialScrapeError).Failed) + } + }) + } +} + +func getExpectedLengthOfReturnedMetrics(nameError, exeError, timeError, memError, diskError error) (int, int) { + if nameError != nil || exeError != nil { + return 0, 0 + } + + expectedLen := 0 + if timeError == nil { + expectedLen += cpuMetricsLen + } + if memError == nil { + expectedLen += memoryMetricsLen + } + if diskError == nil { + expectedLen += diskMetricsLen + } + return 1, expectedLen +} + +func getExpectedScrapeFailures(nameError, exeError, timeError, memError, diskError error) int { + expectedResourceMetricsLen, expectedMetricsLen := getExpectedLengthOfReturnedMetrics(nameError, exeError, timeError, memError, diskError) + if expectedResourceMetricsLen == 0 { + return 1 + } + + return metricsLen - expectedMetricsLen +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_windows.go new file mode 100644 index 00000000000..45d0dfd345c --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/processscraper/process_scraper_windows.go @@ -0,0 +1,71 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package processscraper + +import ( + "path/filepath" + "regexp" + + "github.com/shirou/gopsutil/cpu" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const cpuStatesLen = 2 + +func appendCPUTimeStateDataPoints(ddps pdata.DoubleDataPointSlice, startTime, now pdata.TimestampUnixNano, cpuTime *cpu.TimesStat) { + initializeCPUTimeDataPoint(ddps.At(0), startTime, now, cpuTime.User, userStateLabelValue) + initializeCPUTimeDataPoint(ddps.At(1), startTime, now, cpuTime.System, systemStateLabelValue) +} + +func initializeCPUTimeDataPoint(dataPoint pdata.DoubleDataPoint, startTime, now pdata.TimestampUnixNano, value float64, stateLabel string) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(stateLabelName, stateLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func getProcessExecutable(proc processHandle) (*executableMetadata, error) { + exe, err := proc.Exe() + if err != nil { + return nil, err + } + + name := filepath.Base(exe) + executable := &executableMetadata{name: name, path: exe} + return executable, nil +} + +// matches the first argument before an unquoted space or slash +var cmdRegex = regexp.MustCompile(`^((?:[^"]*?"[^"]*?")*?[^"]*?)(?:[ \/]|$)`) + +func getProcessCommand(proc processHandle) (*commandMetadata, error) { + cmdline, err := proc.Cmdline() + if err != nil { + return nil, err + } + + cmd := cmdline + match := cmdRegex.FindStringSubmatch(cmdline) + if match != nil { + cmd = match[1] + } + + command := &commandMetadata{command: cmd, commandLine: cmdline} + return command, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/config.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/config.go new file mode 100644 index 00000000000..dddf062ccb9 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/config.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swapscraper + +import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + +// Config relating to Swap Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory.go new file mode 100644 index 00000000000..88ce7250b6c --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swapscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +// This file implements Factory for Swap scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "swap" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (scraperhelper.MetricsScraper, error) { + cfg := config.(*Config) + s := newSwapScraper(ctx, cfg) + + ms := scraperhelper.NewMetricsScraper( + TypeStr, + s.scrape, + scraperhelper.WithStart(s.start), + ) + + return ms, nil +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory_test.go new file mode 100644 index 00000000000..a3668fdcf8d --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/factory_test.go @@ -0,0 +1,38 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swapscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + assert.NoError(t, err) + assert.NotNil(t, scraper) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/pagefile_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/pagefile_windows.go new file mode 100644 index 00000000000..e25b1d9620a --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/pagefile_windows.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package swapscraper + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modKernel32 = windows.NewLazySystemDLL("kernel32.dll") + modPsapi = windows.NewLazySystemDLL("psapi.dll") + + procGetNativeSystemInfo = modKernel32.NewProc("GetNativeSystemInfo") + procEnumPageFilesW = modPsapi.NewProc("EnumPageFilesW") +) + +type systemInfo struct { + wProcessorArchitecture uint16 + wReserved uint16 + dwPageSize uint32 + lpMinimumApplicationAddress uintptr + lpMaximumApplicationAddress uintptr + dwActiveProcessorMask uintptr + dwNumberOfProcessors uint32 + dwProcessorType uint32 + dwAllocationGranularity uint32 + wProcessorLevel uint16 + wProcessorRevision uint16 +} + +func getPageSize() uint64 { + var sysInfo systemInfo + procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sysInfo))) + return uint64(sysInfo.dwPageSize) +} + +type pageFileData struct { + name string + usedPages uint64 + totalPages uint64 +} + +// system type as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-enum_page_file_information +type enumPageFileInformation struct { + cb uint32 + reserved uint32 + totalSize uint64 + totalInUse uint64 + peakUsage uint64 +} + +func getPageFileStats() ([]*pageFileData, error) { + // the following system call invokes the supplied callback function once for each page file before returning + // see https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumpagefilesw + var pageFiles []*pageFileData + result, _, _ := procEnumPageFilesW.Call(windows.NewCallback(pEnumPageFileCallbackW), uintptr(unsafe.Pointer(&pageFiles))) + if result == 0 { + return nil, windows.GetLastError() + } + + return pageFiles, nil +} + +// system callback as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/nc-psapi-penum_page_file_callbackw +func pEnumPageFileCallbackW(pageFiles *[]*pageFileData, enumPageFileInfo *enumPageFileInformation, lpFilenamePtr *[syscall.MAX_LONG_PATH]uint16) *bool { + pageFileName := syscall.UTF16ToString((*lpFilenamePtr)[:]) + + pfData := &pageFileData{ + name: pageFileName, + usedPages: enumPageFileInfo.totalInUse, + totalPages: enumPageFileInfo.totalSize, + } + + *pageFiles = append(*pageFiles, pfData) + + // return true to continue enumerating page files + ret := true + return &ret +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_metadata.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_metadata.go new file mode 100644 index 00000000000..649b14c1042 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_metadata.go @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swapscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// labels + +const ( + deviceLabelName = "device" + directionLabelName = "direction" + stateLabelName = "state" + typeLabelName = "type" +) + +// direction label values + +const ( + inDirectionLabelValue = "page_in" + outDirectionLabelValue = "page_out" +) + +// state label values + +const ( + cachedLabelValue = "cached" + freeLabelValue = "free" + usedLabelValue = "used" +) + +// type label values + +const ( + majorTypeLabelValue = "major" + minorTypeLabelValue = "minor" +) + +var swapUsageDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.swap.usage") + metric.SetDescription("Swap (unix) or pagefile (windows) usage.") + metric.SetUnit("bytes") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(false) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var swapPagingDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.swap.paging_ops") + metric.SetDescription("The number of paging operations.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() + +var swapPageFaultsDescriptor = func() pdata.Metric { + metric := pdata.NewMetric() + metric.SetName("system.swap.page_faults") + metric.SetDescription("The number of page faults.") + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return metric +}() diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others.go new file mode 100644 index 00000000000..1de7e3afb77 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others.go @@ -0,0 +1,160 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package swapscraper + +import ( + "context" + "time" + + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/mem" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +const ( + swapUsageMetricsLen = 1 + pagingMetricsLen = 2 +) + +// scraper for Swap Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + + // for mocking + bootTime func() (uint64, error) + virtualMemory func() (*mem.VirtualMemoryStat, error) + swapMemory func() (*mem.SwapMemoryStat, error) +} + +// newSwapScraper creates a Swap Scraper +func newSwapScraper(_ context.Context, cfg *Config) *scraper { + return &scraper{config: cfg, bootTime: host.BootTime, virtualMemory: mem.VirtualMemory, swapMemory: mem.SwapMemory} +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + return nil +} + +func (s *scraper) scrape(_ context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + var errors []error + + err := s.scrapeAndAppendSwapUsageMetric(metrics) + if err != nil { + errors = append(errors, err) + } + + err = s.scrapeAndAppendPagingMetrics(metrics) + if err != nil { + errors = append(errors, err) + } + + return metrics, scraperhelper.CombineScrapeErrors(errors) +} + +func (s *scraper) scrapeAndAppendSwapUsageMetric(metrics pdata.MetricSlice) error { + now := internal.TimeToUnixNano(time.Now()) + vmem, err := s.virtualMemory() + if err != nil { + return consumererror.NewPartialScrapeError(err, swapUsageMetricsLen) + } + + idx := metrics.Len() + metrics.Resize(idx + swapUsageMetricsLen) + initializeSwapUsageMetric(metrics.At(idx), now, vmem) + return nil +} + +func initializeSwapUsageMetric(metric pdata.Metric, now pdata.TimestampUnixNano, vmem *mem.VirtualMemoryStat) { + swapUsageDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(3) + initializeSwapUsageDataPoint(idps.At(0), now, usedLabelValue, int64(vmem.SwapTotal-vmem.SwapFree-vmem.SwapCached)) + initializeSwapUsageDataPoint(idps.At(1), now, freeLabelValue, int64(vmem.SwapFree)) + initializeSwapUsageDataPoint(idps.At(2), now, cachedLabelValue, int64(vmem.SwapCached)) +} + +func initializeSwapUsageDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, stateLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(stateLabelName, stateLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func (s *scraper) scrapeAndAppendPagingMetrics(metrics pdata.MetricSlice) error { + now := internal.TimeToUnixNano(time.Now()) + swap, err := s.swapMemory() + if err != nil { + return consumererror.NewPartialScrapeError(err, pagingMetricsLen) + } + + idx := metrics.Len() + metrics.Resize(idx + pagingMetricsLen) + initializePagingMetric(metrics.At(idx+0), s.startTime, now, swap) + initializePageFaultsMetric(metrics.At(idx+1), s.startTime, now, swap) + return nil +} + +func initializePagingMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, swap *mem.SwapMemoryStat) { + swapPagingDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(4) + initializePagingDataPoint(idps.At(0), startTime, now, majorTypeLabelValue, inDirectionLabelValue, int64(swap.Sin)) + initializePagingDataPoint(idps.At(1), startTime, now, majorTypeLabelValue, outDirectionLabelValue, int64(swap.Sout)) + initializePagingDataPoint(idps.At(2), startTime, now, minorTypeLabelValue, inDirectionLabelValue, int64(swap.PgIn)) + initializePagingDataPoint(idps.At(3), startTime, now, minorTypeLabelValue, outDirectionLabelValue, int64(swap.PgOut)) +} + +func initializePagingDataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, typeLabel string, directionLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(typeLabelName, typeLabel) + labelsMap.Insert(directionLabelName, directionLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func initializePageFaultsMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, swap *mem.SwapMemoryStat) { + swapPageFaultsDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(1) + initializePageFaultDataPoint(idps.At(0), startTime, now, minorTypeLabelValue, int64(swap.PgFault)) + // TODO add swap.PgMajFault once available in gopsutil +} + +func initializePageFaultDataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, typeLabel string, value int64) { + dataPoint.LabelsMap().Insert(typeLabelName, typeLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others_test.go new file mode 100644 index 00000000000..b4b908feca7 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_others_test.go @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package swapscraper + +import ( + "context" + "errors" + "testing" + + "github.com/shirou/gopsutil/mem" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" +) + +func TestScrape_Errors(t *testing.T) { + type testCase struct { + name string + virtualMemoryFunc func() (*mem.VirtualMemoryStat, error) + swapMemoryFunc func() (*mem.SwapMemoryStat, error) + expectedError string + expectedErrCount int + } + + testCases := []testCase{ + { + name: "virtualMemoryError", + virtualMemoryFunc: func() (*mem.VirtualMemoryStat, error) { return nil, errors.New("err1") }, + expectedError: "err1", + expectedErrCount: swapUsageMetricsLen, + }, + { + name: "swapMemoryError", + swapMemoryFunc: func() (*mem.SwapMemoryStat, error) { return nil, errors.New("err2") }, + expectedError: "err2", + expectedErrCount: pagingMetricsLen, + }, + { + name: "multipleErrors", + virtualMemoryFunc: func() (*mem.VirtualMemoryStat, error) { return nil, errors.New("err1") }, + swapMemoryFunc: func() (*mem.SwapMemoryStat, error) { return nil, errors.New("err2") }, + expectedError: "[err1; err2]", + expectedErrCount: swapUsageMetricsLen + pagingMetricsLen, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newSwapScraper(context.Background(), &Config{}) + if test.virtualMemoryFunc != nil { + scraper.virtualMemory = test.virtualMemoryFunc + } + if test.swapMemoryFunc != nil { + scraper.swapMemory = test.swapMemoryFunc + } + + err := scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize swap scraper: %v", err) + + _, err = scraper.scrape(context.Background()) + assert.EqualError(t, err, test.expectedError) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, test.expectedErrCount, err.(consumererror.PartialScrapeError).Failed) + } + }) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_test.go new file mode 100644 index 00000000000..4331fedc285 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_test.go @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swapscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +func TestScrape(t *testing.T) { + type testCase struct { + name string + bootTimeFunc func() (uint64, error) + expectedStartTime pdata.TimestampUnixNano + initializationErr string + } + + testCases := []testCase{ + { + name: "Standard", + }, + { + name: "Validate Start Time", + bootTimeFunc: func() (uint64, error) { return 100, nil }, + expectedStartTime: 100 * 1e9, + }, + { + name: "Boot Time Error", + bootTimeFunc: func() (uint64, error) { return 0, errors.New("err1") }, + initializationErr: "err1", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newSwapScraper(context.Background(), &Config{}) + if test.bootTimeFunc != nil { + scraper.bootTime = test.bootTimeFunc + } + + err := scraper.start(context.Background(), componenttest.NewNopHost()) + if test.initializationErr != "" { + assert.EqualError(t, err, test.initializationErr) + return + } + require.NoError(t, err, "Failed to initialize swap scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + require.NoError(t, err) + + // expect 3 metrics (windows does not currently support page_faults metric) + expectedMetrics := 3 + if runtime.GOOS == "windows" { + expectedMetrics = 2 + } + assert.Equal(t, expectedMetrics, metrics.Len()) + + assertSwapUsageMetricValid(t, metrics.At(0)) + internal.AssertSameTimeStampForMetrics(t, metrics, 0, 1) + + assertPagingMetricValid(t, metrics.At(1), test.expectedStartTime) + if runtime.GOOS != "windows" { + assertPageFaultsMetricValid(t, metrics.At(2), test.expectedStartTime) + } + internal.AssertSameTimeStampForMetrics(t, metrics, 1, metrics.Len()) + }) + } +} + +func assertSwapUsageMetricValid(t *testing.T, hostSwapUsageMetric pdata.Metric) { + internal.AssertDescriptorEqual(t, swapUsageDescriptor, hostSwapUsageMetric) + + // it's valid for a system to have no swap space / paging file, so if no data points were returned, do no validation + if hostSwapUsageMetric.IntSum().DataPoints().Len() == 0 { + return + } + + // expect at least used, free & cached datapoint + expectedDataPoints := 3 + // windows does not return a cached datapoint + if runtime.GOOS == "windows" { + expectedDataPoints = 2 + } + + assert.GreaterOrEqual(t, hostSwapUsageMetric.IntSum().DataPoints().Len(), expectedDataPoints) + internal.AssertIntSumMetricLabelHasValue(t, hostSwapUsageMetric, 0, stateLabelName, usedLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, hostSwapUsageMetric, 1, stateLabelName, freeLabelValue) + // on non-windows, also expect a cached state label + if runtime.GOOS != "windows" { + internal.AssertIntSumMetricLabelHasValue(t, hostSwapUsageMetric, 2, stateLabelName, cachedLabelValue) + } + // on windows, also expect the page file device name label + if runtime.GOOS == "windows" { + internal.AssertIntSumMetricLabelExists(t, hostSwapUsageMetric, 0, deviceLabelName) + internal.AssertIntSumMetricLabelExists(t, hostSwapUsageMetric, 1, deviceLabelName) + } +} + +func assertPagingMetricValid(t *testing.T, pagingMetric pdata.Metric, startTime pdata.TimestampUnixNano) { + internal.AssertDescriptorEqual(t, swapPagingDescriptor, pagingMetric) + if startTime != 0 { + internal.AssertIntSumMetricStartTimeEquals(t, pagingMetric, startTime) + } + + // expect an in & out datapoint, for both major and minor paging types (windows does not currently support minor paging data) + expectedDataPoints := 4 + if runtime.GOOS == "windows" { + expectedDataPoints = 2 + } + assert.Equal(t, expectedDataPoints, pagingMetric.IntSum().DataPoints().Len()) + + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 0, typeLabelName, majorTypeLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 0, directionLabelName, inDirectionLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 1, typeLabelName, majorTypeLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 1, directionLabelName, outDirectionLabelValue) + if runtime.GOOS != "windows" { + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 2, typeLabelName, minorTypeLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 2, directionLabelName, inDirectionLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 3, typeLabelName, minorTypeLabelValue) + internal.AssertIntSumMetricLabelHasValue(t, pagingMetric, 3, directionLabelName, outDirectionLabelValue) + } +} + +func assertPageFaultsMetricValid(t *testing.T, pageFaultsMetric pdata.Metric, startTime pdata.TimestampUnixNano) { + internal.AssertDescriptorEqual(t, swapPageFaultsDescriptor, pageFaultsMetric) + if startTime != 0 { + internal.AssertIntSumMetricStartTimeEquals(t, pageFaultsMetric, startTime) + } + + assert.Equal(t, 1, pageFaultsMetric.IntSum().DataPoints().Len()) + internal.AssertIntSumMetricLabelHasValue(t, pageFaultsMetric, 0, typeLabelName, minorTypeLabelValue) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows.go new file mode 100644 index 00000000000..a48baae482e --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows.go @@ -0,0 +1,177 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package swapscraper + +import ( + "context" + "sync" + "time" + + "github.com/shirou/gopsutil/host" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/perfcounters" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +const ( + swapUsageMetricsLen = 1 + pagingMetricsLen = 1 + + memory = "Memory" + + pageReadsPerSec = "Page Reads/sec" + pageWritesPerSec = "Page Writes/sec" +) + +// scraper for Swap Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + + pageSize uint64 + + perfCounterScraper perfcounters.PerfCounterScraper + + // for mocking + bootTime func() (uint64, error) + pageFileStats func() ([]*pageFileData, error) +} + +var ( + once sync.Once + pageSize uint64 +) + +// newSwapScraper creates a Swap Scraper +func newSwapScraper(_ context.Context, cfg *Config) *scraper { + once.Do(func() { pageSize = getPageSize() }) + + return &scraper{config: cfg, pageSize: pageSize, perfCounterScraper: &perfcounters.PerfLibScraper{}, bootTime: host.BootTime, pageFileStats: getPageFileStats} +} + +func (s *scraper) start(context.Context, component.Host) error { + bootTime, err := s.bootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime * 1e9) + + return s.perfCounterScraper.Initialize(memory) +} + +func (s *scraper) scrape(context.Context) (pdata.MetricSlice, error) { + metrics := pdata.NewMetricSlice() + + var errors []error + + err := s.scrapeAndAppendSwapUsageMetric(metrics) + if err != nil { + errors = append(errors, err) + } + + err = s.scrapeAndAppendPagingMetric(metrics) + if err != nil { + errors = append(errors, err) + } + + return metrics, scraperhelper.CombineScrapeErrors(errors) +} + +func (s *scraper) scrapeAndAppendSwapUsageMetric(metrics pdata.MetricSlice) error { + now := internal.TimeToUnixNano(time.Now()) + pageFiles, err := s.pageFileStats() + if err != nil { + return consumererror.NewPartialScrapeError(err, swapUsageMetricsLen) + } + + idx := metrics.Len() + metrics.Resize(idx + swapUsageMetricsLen) + s.initializeSwapUsageMetric(metrics.At(idx), now, pageFiles) + return nil +} + +func (s *scraper) initializeSwapUsageMetric(metric pdata.Metric, now pdata.TimestampUnixNano, pageFiles []*pageFileData) { + swapUsageDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2 * len(pageFiles)) + + idx := 0 + for _, pageFile := range pageFiles { + initializeSwapUsageDataPoint(idps.At(idx+0), now, pageFile.name, usedLabelValue, int64(pageFile.usedPages*s.pageSize)) + initializeSwapUsageDataPoint(idps.At(idx+1), now, pageFile.name, freeLabelValue, int64((pageFile.totalPages-pageFile.usedPages)*s.pageSize)) + idx += 2 + } +} + +func initializeSwapUsageDataPoint(dataPoint pdata.IntDataPoint, now pdata.TimestampUnixNano, deviceLabel string, stateLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(deviceLabelName, deviceLabel) + labelsMap.Insert(stateLabelName, stateLabel) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} + +func (s *scraper) scrapeAndAppendPagingMetric(metrics pdata.MetricSlice) error { + now := internal.TimeToUnixNano(time.Now()) + + counters, err := s.perfCounterScraper.Scrape() + if err != nil { + return consumererror.NewPartialScrapeError(err, pagingMetricsLen) + } + + memoryObject, err := counters.GetObject(memory) + if err != nil { + return consumererror.NewPartialScrapeError(err, pagingMetricsLen) + } + + memoryCounterValues, err := memoryObject.GetValues(pageReadsPerSec, pageWritesPerSec) + if err != nil { + return consumererror.NewPartialScrapeError(err, pagingMetricsLen) + } + + if len(memoryCounterValues) > 0 { + idx := metrics.Len() + metrics.Resize(idx + pagingMetricsLen) + initializePagingMetric(metrics.At(idx), s.startTime, now, memoryCounterValues[0]) + } + + return nil +} + +func initializePagingMetric(metric pdata.Metric, startTime, now pdata.TimestampUnixNano, memoryCounterValues *perfcounters.CounterValues) { + swapPagingDescriptor.CopyTo(metric) + + idps := metric.IntSum().DataPoints() + idps.Resize(2) + initializePagingDataPoint(idps.At(0), startTime, now, inDirectionLabelValue, memoryCounterValues.Values[pageReadsPerSec]) + initializePagingDataPoint(idps.At(1), startTime, now, outDirectionLabelValue, memoryCounterValues.Values[pageWritesPerSec]) +} + +func initializePagingDataPoint(dataPoint pdata.IntDataPoint, startTime, now pdata.TimestampUnixNano, directionLabel string, value int64) { + labelsMap := dataPoint.LabelsMap() + labelsMap.Insert(typeLabelName, majorTypeLabelValue) + labelsMap.Insert(directionLabelName, directionLabel) + dataPoint.SetStartTime(startTime) + dataPoint.SetTimestamp(now) + dataPoint.SetValue(value) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows_test.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows_test.go new file mode 100644 index 00000000000..08ddbbcc177 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/scraper/swapscraper/swap_scraper_windows_test.go @@ -0,0 +1,127 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package swapscraper + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/perfcounters" +) + +func TestScrape_Errors(t *testing.T) { + type testCase struct { + name string + pageSize uint64 + getPageFileStats func() ([]*pageFileData, error) + scrapeErr error + getObjectErr error + getValuesErr error + expectedErr string + expectedErrCount int + expectedUsedValue int64 + expectedFreeValue int64 + } + + testPageSize := uint64(4096) + testPageFileData := &pageFileData{usedPages: 100, totalPages: 300} + + testCases := []testCase{ + { + name: "standard", + pageSize: testPageSize, + getPageFileStats: func() ([]*pageFileData, error) { + return []*pageFileData{testPageFileData}, nil + }, + expectedUsedValue: int64(testPageFileData.usedPages * testPageSize), + expectedFreeValue: int64((testPageFileData.totalPages - testPageFileData.usedPages) * testPageSize), + }, + { + name: "pageFileError", + getPageFileStats: func() ([]*pageFileData, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + expectedErrCount: swapUsageMetricsLen, + }, + { + name: "scrapeError", + scrapeErr: errors.New("err1"), + expectedErr: "err1", + expectedErrCount: pagingMetricsLen, + }, + { + name: "getObjectErr", + getObjectErr: errors.New("err1"), + expectedErr: "err1", + expectedErrCount: pagingMetricsLen, + }, + { + name: "getValuesErr", + getValuesErr: errors.New("err1"), + expectedErr: "err1", + expectedErrCount: pagingMetricsLen, + }, + { + name: "multipleErrors", + getPageFileStats: func() ([]*pageFileData, error) { return nil, errors.New("err1") }, + getObjectErr: errors.New("err2"), + expectedErr: "[err1; err2]", + expectedErrCount: swapUsageMetricsLen + pagingMetricsLen, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newSwapScraper(context.Background(), &Config{}) + if test.getPageFileStats != nil { + scraper.pageFileStats = test.getPageFileStats + } + if test.pageSize > 0 { + scraper.pageSize = test.pageSize + } else { + assert.Greater(t, pageSize, uint64(0)) + assert.Zero(t, pageSize%4096) // page size on Windows should always be a multiple of 4KB + } + scraper.perfCounterScraper = perfcounters.NewMockPerfCounterScraperError(test.scrapeErr, test.getObjectErr, test.getValuesErr) + + err := scraper.start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to initialize swap scraper: %v", err) + + metrics, err := scraper.scrape(context.Background()) + if test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + + isPartial := consumererror.IsPartialScrapeError(err) + assert.True(t, isPartial) + if isPartial { + assert.Equal(t, test.expectedErrCount, err.(consumererror.PartialScrapeError).Failed) + } + + return + } + + swapUsageMetric := metrics.At(0) + assert.Equal(t, test.expectedUsedValue, swapUsageMetric.IntSum().DataPoints().At(0).Value()) + assert.Equal(t, test.expectedFreeValue, swapUsageMetric.IntSum().DataPoints().At(1).Value()) + }) + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/testutils.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/testutils.go new file mode 100644 index 00000000000..ccab4cca48f --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/testutils.go @@ -0,0 +1,110 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func AssertContainsAttribute(t *testing.T, attr pdata.AttributeMap, key string) { + _, ok := attr.Get(key) + assert.True(t, ok) +} + +func AssertDescriptorEqual(t *testing.T, expected pdata.Metric, actual pdata.Metric) { + assert.Equal(t, expected.Name(), actual.Name()) + assert.Equal(t, expected.Description(), actual.Description()) + assert.Equal(t, expected.Unit(), actual.Unit()) + assert.Equal(t, expected.DataType(), actual.DataType()) +} + +func AssertIntSumMetricLabelHasValue(t *testing.T, metric pdata.Metric, index int, labelName string, expectedVal string) { + val, ok := metric.IntSum().DataPoints().At(index).LabelsMap().Get(labelName) + assert.Truef(t, ok, "Missing label %q in metric %q", labelName, metric.Name()) + assert.Equal(t, expectedVal, val) +} + +func AssertIntGaugeMetricLabelHasValue(t *testing.T, metric pdata.Metric, index int, labelName string, expectedVal string) { + val, ok := metric.IntGauge().DataPoints().At(index).LabelsMap().Get(labelName) + assert.Truef(t, ok, "Missing label %q in metric %q", labelName, metric.Name()) + assert.Equal(t, expectedVal, val) +} + +func AssertDoubleSumMetricLabelHasValue(t *testing.T, metric pdata.Metric, index int, labelName string, expectedVal string) { + val, ok := metric.DoubleSum().DataPoints().At(index).LabelsMap().Get(labelName) + assert.Truef(t, ok, "Missing label %q in metric %q", labelName, metric.Name()) + assert.Equal(t, expectedVal, val) +} + +func AssertIntSumMetricLabelExists(t *testing.T, metric pdata.Metric, index int, labelName string) { + _, ok := metric.IntSum().DataPoints().At(index).LabelsMap().Get(labelName) + assert.Truef(t, ok, "Missing label %q in metric %q", labelName, metric.Name()) +} + +func AssertDoubleSumMetricLabelExists(t *testing.T, metric pdata.Metric, index int, labelName string) { + _, ok := metric.DoubleSum().DataPoints().At(index).LabelsMap().Get(labelName) + assert.Truef(t, ok, "Missing label %q in metric %q", labelName, metric.Name()) +} + +func AssertIntSumMetricStartTimeEquals(t *testing.T, metric pdata.Metric, startTime pdata.TimestampUnixNano) { + idps := metric.IntSum().DataPoints() + for i := 0; i < idps.Len(); i++ { + require.Equal(t, startTime, idps.At(i).StartTime()) + } +} + +func AssertDoubleSumMetricStartTimeEquals(t *testing.T, metric pdata.Metric, startTime pdata.TimestampUnixNano) { + ddps := metric.DoubleSum().DataPoints() + for i := 0; i < ddps.Len(); i++ { + require.Equal(t, startTime, ddps.At(i).StartTime()) + } +} + +func AssertSameTimeStampForAllMetrics(t *testing.T, metrics pdata.MetricSlice) { + AssertSameTimeStampForMetrics(t, metrics, 0, metrics.Len()) +} + +func AssertSameTimeStampForMetrics(t *testing.T, metrics pdata.MetricSlice, startIdx, endIdx int) { + var ts pdata.TimestampUnixNano + for i := startIdx; i < endIdx; i++ { + metric := metrics.At(i) + + dt := metric.DataType() + if dt == pdata.MetricDataTypeIntSum { + idps := metric.IntSum().DataPoints() + for j := 0; j < idps.Len(); j++ { + if ts == 0 { + ts = idps.At(j).Timestamp() + } + require.Equalf(t, ts, idps.At(j).Timestamp(), "metrics contained different end timestamp values") + } + } + + if dt == pdata.MetricDataTypeDoubleSum { + ddps := metric.DoubleSum().DataPoints() + for j := 0; j < ddps.Len(); j++ { + if ts == 0 { + ts = ddps.At(j).Timestamp() + } + require.Equalf(t, ts, ddps.At(j).Timestamp(), "metrics contained different end timestamp values") + } + } + } +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/internal/utils.go b/internal/otel_collector/receiver/hostmetricsreceiver/internal/utils.go new file mode 100644 index 00000000000..a4d9cd29d1f --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/internal/utils.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "time" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TimeToUnixNano(t time.Time) pdata.TimestampUnixNano { + return pdata.TimestampUnixNano(uint64(t.UnixNano())) +} diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/metadata.yaml b/internal/otel_collector/receiver/hostmetricsreceiver/metadata.yaml new file mode 100644 index 00000000000..1233137be8a --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/metadata.yaml @@ -0,0 +1,34 @@ +name: hostmetricsreceiver + +labels: + cpu: + description: CPU number starting at 0. + + cpu.state: + value: state + description: Breakdown of CPU usage by type. + enum: [idle, interrupt, nice, softirq, steal, system, user, wait] + + mem.state: + value: state + description: Breakdown of memory usage by type. + enum: [buffered, cached, inactive, free, slab_reclaimable, slab_unreclaimable, used] + +metrics: + system.cpu.time: + description: Total CPU seconds broken down by different states. + unit: s + data: + type: double sum + aggregation: cumulative + monotonic: true + labels: [cpu.state] + + system.memory.usage: + description: Bytes of memory in use. + unit: By + labels: [mem.state] + data: + type: int sum + aggregation: cumulative + monotonic: false diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-invalidscraperkey.yaml b/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-invalidscraperkey.yaml new file mode 100644 index 00000000000..6640f1d1e26 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-invalidscraperkey.yaml @@ -0,0 +1,18 @@ +receivers: + hostmetrics: + scrapers: + invalidscraperkey: + + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + metrics: + receivers: [hostmetrics] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-noscrapers.yaml b/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-noscrapers.yaml new file mode 100644 index 00000000000..94e60d478a4 --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config-noscrapers.yaml @@ -0,0 +1,15 @@ +receivers: + hostmetrics: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + metrics: + receivers: [hostmetrics] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config.yaml b/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config.yaml new file mode 100644 index 00000000000..9e9f2b0e15c --- /dev/null +++ b/internal/otel_collector/receiver/hostmetricsreceiver/testdata/config.yaml @@ -0,0 +1,35 @@ +receivers: + hostmetrics: + scrapers: + cpu: + hostmetrics/customname: + collection_interval: 30s + scrapers: + cpu: + disk: + load: + filesystem: + memory: + network: + include: + interfaces: ["test1"] + match_type: "strict" + processes: + swap: + process: + include: + names: ["test2", "test3"] + match_type: "regexp" + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + metrics: + receivers: [hostmetrics] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/jaegerreceiver/README.md b/internal/otel_collector/receiver/jaegerreceiver/README.md new file mode 100644 index 00000000000..782716ee8c9 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/README.md @@ -0,0 +1,96 @@ +# Jaeger Receiver + +Receives trace data in [Jaeger](https://www.jaegertracing.io/) format. + +Supported pipeline types: traces + +## Getting Started + +By default, the Jaeger receiver will not serve any protocol. A protocol must be +named under the `protocols` object for the jaeger receiver to start. The +below protocols are supported, each supports an optional `endpoint` +object configuration parameter. + +- `grpc` (default `endpoint` = 0.0.0.0:14250) +- `thrift_binary` (default `endpoint` = 0.0.0.0:6832) +- `thrift_compact` (default `endpoint` = 0.0.0.0:6831) +- `thrift_http` (default `endpoint` = 0.0.0.0:14268) + +Examples: + +```yaml +receivers: + jaeger: + protocols: + grpc: + jaeger/withendpoint: + protocols: + grpc: + endpoint: 0.0.0.0:14260 +``` + +## Advanced Configuration + +UDP protocols (currently `thrift_binary` and `thrift_compact`) allow setting additional +server options: + +- `queue_size` (default 1000) sets max not yet handled requests to server +- `max_packet_size` (default 65_000) sets max UDP packet size +- `workers` (default 10) sets number of workers consuming the server queue +- `socket_buffer_size` (default 0 - no buffer) sets buffer size of connection socket in bytes + +Examples: + +```yaml +protocols: + thrift_binary: + endpoint: 0.0.0.0:6832 + queue_size: 5_000 + max_packet_size: 131_072 + workers: 50 + socket_buffer_size: 8_388_608 +``` + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) including CORS +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) + +## Remote Sampling + +The Jaeger receiver also supports fetching sampling configuration from a remote +collector. It works by proxying client requests for remote sampling +configuration to the configured collector. + + +------------+ +-----------+ +---------------+ + | | get | | proxy | | + | client +--- sampling ---->+ agent +------------->+ collector | + | | strategy | | | | + +------------+ +-----------+ +---------------+ + +Remote sample proxying can be enabled by specifying the following lines in the +jaeger receiver config: + +```yaml +receivers: + jaeger: + protocols: + grpc: + remote_sampling: + fetch_endpoint: "jaeger-collector:1234" +``` + +Remote sampling can also be directly served by the collector by providing a +sampling json file: + +```yaml +receivers: + jaeger: + protocols: + grpc: + remote_sampling: + strategy_file: "/etc/strategy.json" +``` + +Note: the `grpc` protocol must be enabled for this to work as Jaeger serves its +remote sampling strategies over gRPC. diff --git a/internal/otel_collector/receiver/jaegerreceiver/config.go b/internal/otel_collector/receiver/jaegerreceiver/config.go new file mode 100644 index 00000000000..8df7bee3fe8 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/config.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +import ( + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" +) + +const ( + // The config field name to load the protocol map from + protocolsFieldName = "protocols" + + // Default UDP server options + defaultQueueSize = 1_000 + defaultMaxPacketSize = 65_000 + defaultServerWorkers = 10 + defaultSocketBufferSize = 0 +) + +// RemoteSamplingConfig defines config key for remote sampling fetch endpoint +type RemoteSamplingConfig struct { + HostEndpoint string `mapstructure:"host_endpoint"` + StrategyFile string `mapstructure:"strategy_file"` + configgrpc.GRPCClientSettings `mapstructure:",squash"` +} + +type Protocols struct { + GRPC *configgrpc.GRPCServerSettings `mapstructure:"grpc"` + ThriftHTTP *confighttp.HTTPServerSettings `mapstructure:"thrift_http"` + ThriftBinary *ProtocolUDP `mapstructure:"thrift_binary"` + ThriftCompact *ProtocolUDP `mapstructure:"thrift_compact"` +} + +type ProtocolUDP struct { + Endpoint string `mapstructure:"endpoint"` + ServerConfigUDP `mapstructure:",squash"` +} + +type ServerConfigUDP struct { + QueueSize int `mapstructure:"queue_size"` + MaxPacketSize int `mapstructure:"max_packet_size"` + Workers int `mapstructure:"workers"` + SocketBufferSize int `mapstructure:"socket_buffer_size"` +} + +func DefaultServerConfigUDP() ServerConfigUDP { + return ServerConfigUDP{ + QueueSize: defaultQueueSize, + MaxPacketSize: defaultMaxPacketSize, + Workers: defaultServerWorkers, + SocketBufferSize: defaultSocketBufferSize, + } +} + +// Config defines configuration for Jaeger receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + Protocols `mapstructure:"protocols"` + RemoteSampling *RemoteSamplingConfig `mapstructure:"remote_sampling"` +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/config_test.go b/internal/otel_collector/receiver/jaegerreceiver/config_test.go new file mode 100644 index 00000000000..50dbd717098 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/config_test.go @@ -0,0 +1,185 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 4) + + r1 := cfg.Receivers["jaeger/customname"].(*Config) + assert.Equal(t, r1, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "jaeger/customname", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:9876", + Transport: "tcp", + }, + }, + ThriftHTTP: &confighttp.HTTPServerSettings{ + Endpoint: ":3456", + }, + ThriftCompact: &ProtocolUDP{ + Endpoint: "0.0.0.0:456", + ServerConfigUDP: ServerConfigUDP{ + QueueSize: 100_000, + MaxPacketSize: 131_072, + Workers: 100, + SocketBufferSize: 65_536, + }, + }, + ThriftBinary: &ProtocolUDP{ + Endpoint: "0.0.0.0:789", + ServerConfigUDP: ServerConfigUDP{ + QueueSize: 1_000, + MaxPacketSize: 65_536, + Workers: 5, + SocketBufferSize: 0, + }, + }, + }, + RemoteSampling: &RemoteSamplingConfig{ + HostEndpoint: "0.0.0.0:5778", + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: "jaeger-collector:1234", + }, + StrategyFile: "/etc/strategies.json", + }, + }) + + rDefaults := cfg.Receivers["jaeger/defaults"].(*Config) + assert.Equal(t, rDefaults, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "jaeger/defaults", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCBindEndpoint, + Transport: "tcp", + }, + }, + ThriftHTTP: &confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPBindEndpoint, + }, + ThriftCompact: &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + ServerConfigUDP: DefaultServerConfigUDP(), + }, + ThriftBinary: &ProtocolUDP{ + Endpoint: defaultThriftBinaryBindEndpoint, + ServerConfigUDP: DefaultServerConfigUDP(), + }, + }, + }) + + rMixed := cfg.Receivers["jaeger/mixed"].(*Config) + assert.Equal(t, rMixed, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "jaeger/mixed", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:9876", + Transport: "tcp", + }, + }, + ThriftCompact: &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + ServerConfigUDP: DefaultServerConfigUDP(), + }, + }, + }) + + tlsConfig := cfg.Receivers["jaeger/tls"].(*Config) + + assert.Equal(t, tlsConfig, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "jaeger/tls", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:9876", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "/test.crt", + KeyFile: "/test.key", + }, + }, + }, + ThriftHTTP: &confighttp.HTTPServerSettings{ + Endpoint: ":3456", + }, + }, + }) +} + +func TestFailedLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_typo_default_proto_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for jaeger: unknown protocols in the Jaeger receiver") + + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_proto_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for jaeger: 1 error(s) decoding:\n\n* 'protocols' has invalid keys: thrift_htttp") + + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_no_proto_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for jaeger: must specify at least one protocol when using the Jaeger receiver") + + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_empty_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for jaeger: empty config for Jaeger receiver") +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/errors.go b/internal/otel_collector/receiver/jaegerreceiver/errors.go new file mode 100644 index 00000000000..41d41846b5a --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/errors.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +type httpError struct { + msg string + statusCode int +} + +func (h httpError) Error() string { + return h.msg +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/factory.go b/internal/otel_collector/receiver/jaegerreceiver/factory.go new file mode 100644 index 00000000000..162e2f536f5 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/factory.go @@ -0,0 +1,255 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +// This file implements factory for Jaeger receiver. + +import ( + "context" + "fmt" + "net" + "strconv" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "jaeger" + + // Protocol values. + protoGRPC = "grpc" + protoThriftHTTP = "thrift_http" + protoThriftBinary = "thrift_binary" + protoThriftCompact = "thrift_compact" + + // Default endpoints to bind to. + defaultGRPCBindEndpoint = "0.0.0.0:14250" + defaultHTTPBindEndpoint = "0.0.0.0:14268" + defaultThriftCompactBindEndpoint = "0.0.0.0:6831" + defaultThriftBinaryBindEndpoint = "0.0.0.0:6832" + defaultAgentRemoteSamplingHTTPPort = 5778 +) + +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithTraces(createTraceReceiver), + receiverhelper.WithCustomUnmarshaler(customUnmarshaler)) +} + +// customUnmarshaler is used to add defaults for named but empty protocols +func customUnmarshaler(componentViperSection *viper.Viper, intoCfg interface{}) error { + if componentViperSection == nil || len(componentViperSection.AllKeys()) == 0 { + return fmt.Errorf("empty config for Jaeger receiver") + } + + componentViperSection.SetConfigType("yaml") + + // UnmarshalExact will not set struct properties to nil even if no key is provided, + // so set the protocol structs to nil where the keys were omitted. + err := componentViperSection.UnmarshalExact(intoCfg) + if err != nil { + return err + } + + receiverCfg := intoCfg.(*Config) + + protocols := componentViperSection.GetStringMap(protocolsFieldName) + if len(protocols) == 0 { + return fmt.Errorf("must specify at least one protocol when using the Jaeger receiver") + } + + knownProtocols := 0 + if _, ok := protocols[protoGRPC]; !ok { + receiverCfg.GRPC = nil + } else { + knownProtocols++ + } + if _, ok := protocols[protoThriftHTTP]; !ok { + receiverCfg.ThriftHTTP = nil + } else { + knownProtocols++ + } + if _, ok := protocols[protoThriftBinary]; !ok { + receiverCfg.ThriftBinary = nil + } else { + knownProtocols++ + } + if _, ok := protocols[protoThriftCompact]; !ok { + receiverCfg.ThriftCompact = nil + } else { + knownProtocols++ + } + // UnmarshalExact will ignore empty entries like a protocol with no values, so if a typo happened + // in the protocol that is intended to be enabled will not be enabled. So check if the protocols + // include only known protocols. + if len(protocols) != knownProtocols { + return fmt.Errorf("unknown protocols in the Jaeger receiver") + } + return nil +} + +// CreateDefaultConfig creates the default configuration for Jaeger receiver. +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCBindEndpoint, + Transport: "tcp", + }, + }, + ThriftHTTP: &confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPBindEndpoint, + }, + ThriftBinary: &ProtocolUDP{ + Endpoint: defaultThriftBinaryBindEndpoint, + ServerConfigUDP: DefaultServerConfigUDP(), + }, + ThriftCompact: &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + ServerConfigUDP: DefaultServerConfigUDP(), + }, + }, + } +} + +// createTraceReceiver creates a trace receiver based on provided config. +func createTraceReceiver( + _ context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer, +) (component.TracesReceiver, error) { + + // Convert settings in the source config to configuration struct + // that Jaeger receiver understands. + + rCfg := cfg.(*Config) + remoteSamplingConfig := rCfg.RemoteSampling + + var config configuration + // Set ports + if rCfg.Protocols.GRPC != nil { + var err error + config.CollectorGRPCPort, err = extractPortFromEndpoint(rCfg.Protocols.GRPC.NetAddr.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to extract port for GRPC: %w", err) + } + + config.CollectorGRPCOptions, err = rCfg.Protocols.GRPC.ToServerOption() + if err != nil { + return nil, err + } + } + + if rCfg.Protocols.ThriftHTTP != nil { + var err error + config.CollectorHTTPPort, err = extractPortFromEndpoint(rCfg.Protocols.ThriftHTTP.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to extract port for ThriftHTTP: %w", err) + } + } + + if rCfg.Protocols.ThriftBinary != nil { + config.AgentBinaryThriftConfig = rCfg.ThriftBinary.ServerConfigUDP + var err error + config.AgentBinaryThriftPort, err = extractPortFromEndpoint(rCfg.Protocols.ThriftBinary.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to extract port for ThriftBinary: %w", err) + } + } + + if rCfg.Protocols.ThriftCompact != nil { + config.AgentCompactThriftConfig = rCfg.ThriftCompact.ServerConfigUDP + var err error + config.AgentCompactThriftPort, err = extractPortFromEndpoint(rCfg.Protocols.ThriftCompact.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to extract port for ThriftCompact: %w", err) + } + } + + if remoteSamplingConfig != nil { + config.RemoteSamplingClientSettings = remoteSamplingConfig.GRPCClientSettings + if len(config.RemoteSamplingClientSettings.Endpoint) == 0 { + config.RemoteSamplingClientSettings.Endpoint = defaultGRPCBindEndpoint + } + + if len(remoteSamplingConfig.HostEndpoint) == 0 { + config.AgentHTTPPort = defaultAgentRemoteSamplingHTTPPort + } else { + var err error + config.AgentHTTPPort, err = extractPortFromEndpoint(remoteSamplingConfig.HostEndpoint) + if err != nil { + return nil, err + } + } + + // strategies are served over grpc so if grpc is not enabled and strategies are present return an error + if len(remoteSamplingConfig.StrategyFile) != 0 { + if config.CollectorGRPCPort == 0 { + return nil, fmt.Errorf("strategy file requires the GRPC protocol to be enabled") + } + + config.RemoteSamplingStrategyFile = remoteSamplingConfig.StrategyFile + } + } + + if (rCfg.Protocols.GRPC == nil && rCfg.Protocols.ThriftHTTP == nil && rCfg.Protocols.ThriftBinary == nil && rCfg.Protocols.ThriftCompact == nil) || + (config.CollectorGRPCPort == 0 && config.CollectorHTTPPort == 0 && config.CollectorThriftPort == 0 && config.AgentBinaryThriftPort == 0 && config.AgentCompactThriftPort == 0) { + err := fmt.Errorf("either GRPC(%v), ThriftHTTP(%v), ThriftCompact(%v), or ThriftBinary(%v) protocol endpoint with non-zero port must be enabled for %s receiver", + rCfg.Protocols.GRPC, + rCfg.Protocols.ThriftHTTP, + rCfg.Protocols.ThriftCompact, + rCfg.Protocols.ThriftBinary, + typeStr, + ) + return nil, err + } + + // Create the receiver. + return newJaegerReceiver(rCfg.Name(), &config, nextConsumer, params), nil +} + +// extract the port number from string in "address:port" format. If the +// port number cannot be extracted returns an error. +func extractPortFromEndpoint(endpoint string) (int, error) { + _, portStr, err := net.SplitHostPort(endpoint) + if err != nil { + return 0, fmt.Errorf("endpoint is not formatted correctly: %s", err.Error()) + } + port, err := strconv.ParseInt(portStr, 10, 0) + if err != nil { + return 0, fmt.Errorf("endpoint port is not a number: %s", err.Error()) + } + if port < 1 || port > 65535 { + return 0, fmt.Errorf("port number must be between 1 and 65535") + } + return int(port), nil +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/factory_test.go b/internal/otel_collector/receiver/jaegerreceiver/factory_test.go new file mode 100644 index 00000000000..728b83674d6 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/factory_test.go @@ -0,0 +1,352 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +import ( + "context" + "fmt" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" +) + +func TestTypeStr(t *testing.T) { + factory := NewFactory() + + assert.Equal(t, "jaeger", string(factory.Type())) +} + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + // have to enable at least one protocol for the jaeger receiver to be created + cfg.(*Config).Protocols.GRPC = &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCBindEndpoint, + Transport: "tcp", + }, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + tReceiver, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.NoError(t, err, "receiver creation failed") + assert.NotNil(t, tReceiver, "receiver creation failed") + + mReceiver, err := factory.CreateMetricsReceiver(context.Background(), params, cfg, nil) + assert.Equal(t, err, configerror.ErrDataTypeIsNotSupported) + assert.Nil(t, mReceiver) +} + +func TestCreateReceiverGeneralConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + require.NoError(t, err) + require.NotNil(t, cfg) + + rCfg, ok := cfg.Receivers["jaeger/customname"] + require.True(t, ok) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + tReceiver, err := factory.CreateTracesReceiver(context.Background(), params, rCfg, nil) + assert.NoError(t, err, "receiver creation failed") + assert.NotNil(t, tReceiver, "receiver creation failed") + + mReceiver, err := factory.CreateMetricsReceiver(context.Background(), params, rCfg, nil) + assert.Equal(t, err, configerror.ErrDataTypeIsNotSupported) + assert.Nil(t, mReceiver) +} + +// default ports retrieved from https://www.jaegertracing.io/docs/1.16/deployment/ +func TestCreateDefaultGRPCEndpoint(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.GRPC = &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCBindEndpoint, + Transport: "tcp", + }, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "unexpected error creating receiver") + assert.Equal(t, 14250, r.(*jReceiver).config.CollectorGRPCPort, "grpc port should be default") +} + +func TestCreateTLSGPRCEndpoint(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.GRPC = &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCBindEndpoint, + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "./testdata/server.crt", + KeyFile: "./testdata/server.key", + }, + }, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.NoError(t, err, "tls-enabled receiver creation failed") +} + +func TestCreateInvalidHTTPEndpoint(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftHTTP = &confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPBindEndpoint, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "unexpected error creating receiver") + assert.Equal(t, 14268, r.(*jReceiver).config.CollectorHTTPPort, "http port should be default") +} + +func TestCreateInvalidThriftBinaryEndpoint(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftBinary = &ProtocolUDP{ + Endpoint: defaultThriftBinaryBindEndpoint, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "unexpected error creating receiver") + assert.Equal(t, 6832, r.(*jReceiver).config.AgentBinaryThriftPort, "thrift port should be default") +} + +func TestCreateInvalidThriftCompactEndpoint(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftCompact = &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "unexpected error creating receiver") + assert.Equal(t, 6831, r.(*jReceiver).config.AgentCompactThriftPort, "thrift port should be default") +} + +func TestDefaultAgentRemoteSamplingEndpointAndPort(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + rCfg := cfg.(*Config) + + rCfg.Protocols.ThriftCompact = &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + } + rCfg.RemoteSampling = &RemoteSamplingConfig{} + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "create trace receiver should not error") + assert.Equal(t, defaultGRPCBindEndpoint, r.(*jReceiver).config.RemoteSamplingClientSettings.Endpoint) + assert.Equal(t, defaultAgentRemoteSamplingHTTPPort, r.(*jReceiver).config.AgentHTTPPort, "agent http port should be default") +} + +func TestAgentRemoteSamplingEndpoint(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + rCfg := cfg.(*Config) + + endpoint := "localhost:1234" + rCfg.Protocols.ThriftCompact = &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + } + rCfg.RemoteSampling = &RemoteSamplingConfig{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + }, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "create trace receiver should not error") + assert.Equal(t, endpoint, r.(*jReceiver).config.RemoteSamplingClientSettings.Endpoint) + assert.Equal(t, defaultAgentRemoteSamplingHTTPPort, r.(*jReceiver).config.AgentHTTPPort, "agent http port should be default") +} + +func TestCreateNoPort(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftHTTP = &confighttp.HTTPServerSettings{ + Endpoint: "localhost:", + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.Error(t, err, "receiver creation with no port number must fail") +} + +func TestCreateLargePort(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftHTTP = &confighttp.HTTPServerSettings{ + Endpoint: "localhost:65536", + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.Error(t, err, "receiver creation with too large port number must fail") +} + +func TestCreateInvalidHost(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.GRPC = &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "1234", + Transport: "tcp", + }, + } + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.Error(t, err, "receiver creation with bad hostname must fail") +} + +func TestCreateNoProtocols(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols = Protocols{} + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.Error(t, err, "receiver creation with no protocols must fail") +} + +func TestThriftBinaryBadPort(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftBinary = &ProtocolUDP{ + Endpoint: "localhost:65536", + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.Error(t, err, "receiver creation with a bad thrift binary port must fail") +} + +func TestThriftCompactBadPort(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + cfg.(*Config).Protocols.ThriftCompact = &ProtocolUDP{ + Endpoint: "localhost:65536", + } + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + assert.Error(t, err, "receiver creation with a bad thrift compact port must fail") +} + +func TestRemoteSamplingConfigPropagation(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + rCfg := cfg.(*Config) + + hostPort := 5778 + endpoint := "localhost:1234" + strategyFile := "strategies.json" + rCfg.Protocols.GRPC = &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCBindEndpoint, + Transport: "tcp", + }, + } + rCfg.RemoteSampling = &RemoteSamplingConfig{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: endpoint, + }, + HostEndpoint: fmt.Sprintf("localhost:%d", hostPort), + StrategyFile: strategyFile, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + r, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.NoError(t, err, "create trace receiver should not error") + assert.Equal(t, endpoint, r.(*jReceiver).config.RemoteSamplingClientSettings.Endpoint) + assert.Equal(t, hostPort, r.(*jReceiver).config.AgentHTTPPort, "agent http port should be configured value") + assert.Equal(t, strategyFile, r.(*jReceiver).config.RemoteSamplingStrategyFile) +} + +func TestRemoteSamplingFileRequiresGRPC(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + rCfg := cfg.(*Config) + + // Remove all default protocols + rCfg.Protocols = Protocols{} + rCfg.Protocols.ThriftCompact = &ProtocolUDP{ + Endpoint: defaultThriftCompactBindEndpoint, + } + rCfg.RemoteSampling = &RemoteSamplingConfig{ + StrategyFile: "strategies.json", + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, nil) + + assert.Error(t, err, "create trace receiver should error") +} + +func TestCustomUnmarshalErrors(t *testing.T) { + factory := NewFactory() + + fu, ok := factory.(component.ConfigUnmarshaler) + assert.True(t, ok) + + err := fu.Unmarshal(config.NewViper(), nil) + assert.Error(t, err, "should not have been able to marshal to a nil config") + + err = fu.Unmarshal(config.NewViper(), &RemoteSamplingConfig{}) + assert.Error(t, err, "should not have been able to marshal to a non-jaegerreceiver config") +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/jaeger_agent_test.go b/internal/otel_collector/receiver/jaegerreceiver/jaeger_agent_test.go new file mode 100644 index 00000000000..3e7a12a1526 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/jaeger_agent_test.go @@ -0,0 +1,253 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +import ( + "context" + "fmt" + "net" + "net/http" + "testing" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/jaegertracing/jaeger/cmd/agent/app/servers/thriftudp" + "github.com/jaegertracing/jaeger/model" + jaegerconvert "github.com/jaegertracing/jaeger/model/converter/thrift/jaeger" + "github.com/jaegertracing/jaeger/proto-gen/api_v2" + "github.com/jaegertracing/jaeger/thrift-gen/agent" + jaegerthrift "github.com/jaegertracing/jaeger/thrift-gen/jaeger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/conventions" + "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +const jaegerAgent = "jaeger_agent_test" + +func TestJaegerAgentUDP_ThriftCompact(t *testing.T) { + port := testutil.GetAvailablePort(t) + addrForClient := fmt.Sprintf(":%d", port) + testJaegerAgent(t, addrForClient, &configuration{ + AgentCompactThriftPort: int(port), + AgentCompactThriftConfig: DefaultServerConfigUDP(), + }) +} + +func TestJaegerAgentUDP_ThriftCompact_InvalidPort(t *testing.T) { + port := 999999 + + config := &configuration{ + AgentCompactThriftPort: port, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerAgent, config, nil, params) + + assert.Error(t, jr.Start(context.Background(), componenttest.NewNopHost()), "should not have been able to startTraceReception") + + jr.Shutdown(context.Background()) +} + +func TestJaegerAgentUDP_ThriftBinary(t *testing.T) { + port := testutil.GetAvailablePort(t) + addrForClient := fmt.Sprintf(":%d", port) + testJaegerAgent(t, addrForClient, &configuration{ + AgentBinaryThriftPort: int(port), + AgentBinaryThriftConfig: DefaultServerConfigUDP(), + }) +} + +func TestJaegerAgentUDP_ThriftBinary_PortInUse(t *testing.T) { + // This test confirms that the thrift binary port is opened correctly. This is all we can test at the moment. See above. + port := testutil.GetAvailablePort(t) + + config := &configuration{ + AgentBinaryThriftPort: int(port), + AgentBinaryThriftConfig: DefaultServerConfigUDP(), + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerAgent, config, nil, params) + + assert.NoError(t, jr.startAgent(componenttest.NewNopHost()), "Start failed") + defer jr.Shutdown(context.Background()) + + l, err := net.Listen("udp", fmt.Sprintf("localhost:%d", port)) + assert.Error(t, err, "should not have been able to listen to the port") + + if l != nil { + l.Close() + } +} + +func TestJaegerAgentUDP_ThriftBinary_InvalidPort(t *testing.T) { + port := 999999 + + config := &configuration{ + AgentBinaryThriftPort: port, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerAgent, config, nil, params) + + assert.Error(t, jr.Start(context.Background(), componenttest.NewNopHost()), "should not have been able to startTraceReception") + + jr.Shutdown(context.Background()) +} + +func initializeGRPCTestServer(t *testing.T, beforeServe func(server *grpc.Server), opts ...grpc.ServerOption) (*grpc.Server, net.Addr) { + server := grpc.NewServer(opts...) + lis, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + beforeServe(server) + go func() { + err := server.Serve(lis) + require.NoError(t, err) + }() + return server, lis.Addr() +} + +type mockSamplingHandler struct { +} + +func (*mockSamplingHandler) GetSamplingStrategy(context.Context, *api_v2.SamplingStrategyParameters) (*api_v2.SamplingStrategyResponse, error) { + return &api_v2.SamplingStrategyResponse{StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC}, nil +} + +func TestJaegerHTTP(t *testing.T) { + s, addr := initializeGRPCTestServer(t, func(s *grpc.Server) { + api_v2.RegisterSamplingManagerServer(s, &mockSamplingHandler{}) + }) + defer s.GracefulStop() + + port := testutil.GetAvailablePort(t) + config := &configuration{ + AgentHTTPPort: int(port), + RemoteSamplingClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: addr.String(), + TLSSetting: configtls.TLSClientSetting{ + Insecure: true, + }, + }, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerAgent, config, nil, params) + defer jr.Shutdown(context.Background()) + + assert.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost()), "Start failed") + + // allow http server to start + assert.NoError(t, testutil.WaitForPort(t, port), "WaitForPort failed") + + resp, err := http.Get(fmt.Sprintf("http://localhost:%d/sampling?service=test", port)) + assert.NoError(t, err, "should not have failed to make request") + if resp != nil { + assert.Equal(t, 200, resp.StatusCode, "should have returned 200") + } + + resp, err = http.Get(fmt.Sprintf("http://localhost:%d/sampling?service=test", port)) + assert.NoError(t, err, "should not have failed to make request") + if resp != nil { + assert.Equal(t, 200, resp.StatusCode, "should have returned 200") + } + + resp, err = http.Get(fmt.Sprintf("http://localhost:%d/baggageRestrictions?service=test", port)) + assert.NoError(t, err, "should not have failed to make request") + if resp != nil { + assert.Equal(t, 200, resp.StatusCode, "should have returned 200") + } +} + +func testJaegerAgent(t *testing.T, agentEndpoint string, receiverConfig *configuration) { + // 1. Create the Jaeger receiver aka "server" + sink := new(consumertest.TracesSink) + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerAgent, receiverConfig, sink, params) + defer jr.Shutdown(context.Background()) + + assert.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost()), "Start failed") + + // 2. Then send spans to the Jaeger receiver. + jexp, err := newClientUDP(agentEndpoint, jr.agentBinaryThriftEnabled()) + assert.NoError(t, err, "Failed to create the Jaeger OpenCensus exporter for the live application") + + // 3. Now finally send some spans + td := generateTraceData() + batches, err := jaeger.InternalTracesToJaegerProto(td) + require.NoError(t, err) + for _, batch := range batches { + require.NoError(t, jexp.EmitBatch(context.Background(), modelToThrift(batch))) + } + + testutil.WaitFor(t, func() bool { + return sink.SpansCount() > 0 + }) + + gotTraces := sink.AllTraces() + require.Equal(t, 1, len(gotTraces)) + assert.EqualValues(t, td, gotTraces[0]) +} + +func newClientUDP(hostPort string, binary bool) (*agent.AgentClient, error) { + clientTransport, err := thriftudp.NewTUDPClientTransport(hostPort, "") + if err != nil { + return nil, err + } + var protocolFactory thrift.TProtocolFactory + if binary { + protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() + } else { + protocolFactory = thrift.NewTCompactProtocolFactory() + } + return agent.NewAgentClientFactory(clientTransport, protocolFactory), nil +} + +// Cannot use the testdata because timestamps are nanoseconds. +func generateTraceData() pdata.Traces { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).Resource().Attributes().UpsertString(conventions.AttributeServiceName, "test") + td.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + span := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + span.SetSpanID(pdata.NewSpanID([8]byte{0, 1, 2, 3, 4, 5, 6, 7})) + span.SetTraceID(pdata.NewTraceID([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1, 0})) + span.SetStartTime(1581452772000000000) + span.SetEndTime(1581452773000000000) + return td +} + +func modelToThrift(batch *model.Batch) *jaegerthrift.Batch { + return &jaegerthrift.Batch{ + Process: processModelToThrift(batch.Process), + Spans: jaegerconvert.FromDomain(batch.Spans), + } +} + +func processModelToThrift(process *model.Process) *jaegerthrift.Process { + if process == nil { + return nil + } + return &jaegerthrift.Process{ + ServiceName: process.ServiceName, + } +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_empty_config.yaml b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_empty_config.yaml new file mode 100644 index 00000000000..a2ff3c2c9f7 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_empty_config.yaml @@ -0,0 +1,15 @@ +receivers: + jaeger: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_no_proto_config.yaml b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_no_proto_config.yaml new file mode 100644 index 00000000000..b213ac63248 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_no_proto_config.yaml @@ -0,0 +1,16 @@ +receivers: + jaeger: + protocols: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_proto_config.yaml b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_proto_config.yaml new file mode 100644 index 00000000000..a87240a9303 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_proto_config.yaml @@ -0,0 +1,19 @@ +receivers: + # The following demonstrates how to enable protocols with defaults + jaeger: + protocols: + thrift_htttp: + endpoint: "127.0.0.1:123" + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_typo_default_proto_config.yaml b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_typo_default_proto_config.yaml new file mode 100644 index 00000000000..753510fd62d --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/bad_typo_default_proto_config.yaml @@ -0,0 +1,20 @@ +receivers: + # The following demonstrates how to enable protocols with defaults + jaeger: + protocols: + grpc: + endpoint: "127.0.0.1:123" + thrift_htttp: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/ca.crt b/internal/otel_collector/receiver/jaegerreceiver/testdata/ca.crt new file mode 100644 index 00000000000..c8d6524ebad --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CCQDq+ERthElKtzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB +VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM +CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIwMDkyMjA1MjIx +MFoXDTMwMDkyMDA1MjIxMFowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry +YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV +BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANv/RHAB8f8VbGG5Wq9mZzqLREoLTfNG8pRCPFvD+UEbl7ldLrzz5T92s4VX7HjA +BsGDTrK7VgO1GZGQXV1fBlbFcAmkISiYWYCmIxD1BfEN+Sh/9OVfKXZJVSInvs/I +nLYvXiymxbtOh/C+/hlcZW9VA2IUkbTUb/qd7SK0pVpOK0KMdpVq5t1HqAP+ssB/ +ZtbWFL1Ai057HNbki+s7LfMeiPya9hY/CRk6ei3oSrxLqQCXUeJAtS/iMzUDyq7u +btDA7sNMUqYvG7nWF9AkUXRqp8DVsIJKGk4hN/aKvkJaJfHe66kirKeJWQXYp5Hh +632EDi8ku4dOVae7w50YnbsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAwEEc13Oi +wvnz6tfhGUVUTfLauNn9qTdXBjNwgQV9z0QZrw9puGAAc1oRs8cmPx+TMROSPzXM +PnRrkFYanh4beg21j4iRVm0rYm796q8IaicerkpN5XzFSeyzwnwMauyOA9cXsMfB +vza8RH+GgUpQ5eZRTuBD03Ic0kfz39bz0gPod6/CWo7ONRV6AoEwVi1vsULLUbA0 +hL/XsjlihXU0XLtEx1DB5lKrATyFPCxR+kq6Q+EdfDq3r+B7rg+gyv6mCzaf5LZY +0+r7s/no+cWzm2LrRebvp00i0RfeqSu3Uwr51oEidkLeBQftQm9Xvkt4Z3O+LJjw +bf40dGXtFmgflw== +-----END CERTIFICATE----- diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/client.crt b/internal/otel_collector/receiver/jaegerreceiver/testdata/client.crt new file mode 100644 index 00000000000..53e9005fdf3 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMddMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjExWhcNMzAwOTIwMDUyMjExWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAzumNUxAx4hbTbru52IxI4OE/SvhKaqbJQffrOY5MFUnblOzIAz1VaaZh +J8nXG+T234KgcZAyW8CCnPyB+LhUxpDVLmeoxbOYM/7ZTsRbUph8yZjkySXii2Qu +pk7L01Q81ESSaSKGBgyc1Go8N/SSwi5XH79Ng/CtDfOLJZdE0gQdoQZFkaX/UZ+t +RFuUhmbOrAk31QnpccKGKOf+8sa16Voy8uv+jdP2QPyARGs5i/1OXGuQ/GnouaFb +o2hgklRPs8or5cLgmcczKoCNDw/Hvw5hys5Isei/e89/IRdtXiHICkIUrAYXOcMi +yZwxN5Gr+A0TvR6rPZqEzoqpFy2S2wIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQC6 +A8DEtVOKTdOXmT0b8eN65w7LJUUPLadEF3YF+osnu2DOBM6GLBcXyX88Km2mvss6 +Yc4H4iBlG7khXBScxPO+ofNC3j3HyklcdUWZNO2GMsnFMhmBXNrN7nBWkLNz67o7 +C1S6Xb+ZbYRTD0VxQ1HDIjYxMSAVxLahl2qvbC0vC3Z7SaDIzkJR2MfHD+d/9CKy +7WkohAs0P1uF7S4DgWjp/c39GRGY4aNgdxnXgbluuho96BlQAnoOaTNaXww6B5mu +7w/kd97Fw7aH5rPLxrs7TkkTDzreW8p7V8VxT7aS3kG7p8k617gVQJNEPLczvGt4 +Dp0mk+luCnY9EzClbAZc +-----END CERTIFICATE----- diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/client.key b/internal/otel_collector/receiver/jaegerreceiver/testdata/client.key new file mode 100644 index 00000000000..8895820fc25 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAzumNUxAx4hbTbru52IxI4OE/SvhKaqbJQffrOY5MFUnblOzI +Az1VaaZhJ8nXG+T234KgcZAyW8CCnPyB+LhUxpDVLmeoxbOYM/7ZTsRbUph8yZjk +ySXii2Qupk7L01Q81ESSaSKGBgyc1Go8N/SSwi5XH79Ng/CtDfOLJZdE0gQdoQZF +kaX/UZ+tRFuUhmbOrAk31QnpccKGKOf+8sa16Voy8uv+jdP2QPyARGs5i/1OXGuQ +/GnouaFbo2hgklRPs8or5cLgmcczKoCNDw/Hvw5hys5Isei/e89/IRdtXiHICkIU +rAYXOcMiyZwxN5Gr+A0TvR6rPZqEzoqpFy2S2wIDAQABAoIBAQCBWynrYBiPjE2q +0NojM7DsRmXXbNq5SoRQJbp9RSTzujT5Kf7xZ4rafhYHVfyRh9d/bJ754HhbPENr ++cEXycXWTf25AT1WPC1PdGhPAhtFy+dX8ao2xuSW2I8BkgmDmQpeenA+IkM/zOrP +MYtsQA7wLyBwzJyde/301umLGsL/uEs3VipWCJ9bk3GuTL6ir4gRdISYd+Rm8tRw +7aeM/uraIv4oK2LInih76xNr+i3vkMDXXzJ+P59Tu6qjt2ksBBjLTuqy+ZkXs2SU +BJ8g9Px/vqSxwTF9aX3nnasA5WyODPx2OTTaHnQ4MTaascG3Sp3Hq+kurPIwnCWG +bZwcuW5hAoGBAOb15byeEg3UssIaXg2HTtp9gShTxDEdoVmTjMTOGrsgZy6ajH6s +AMlOPTG91PH4FNkvxiqdIyN8R5zh+NHKt9pSnbhzFNfvxJG3whrYaE2LwJFhjNTD +rYaTWYS0HaDA/DgKhwEnUmxZaxyaY1ScXcJhAulg3TYU9RnDfVjDgCQtAoGBAOVY +N74ieH4e3EpmdRyH/WtdnTEJ+q/DbUF0LZAAZFoAHyCRMl6KuDObubzMoaoQIT0Q +p3AJCdp8ycwn0WYFL2kP2Wmp/JxDQyj2Uqh2dbU0G+0M3vYgTEvrqZ8pUWO5Dc3C +w2EswcYTq3tzX/kEMegGskkvZ9DlrRpAMRzHAlAnAoGARI0fz0grm6dSF4Kz/9f1 +c6xktY+HX/ync2r0EUYLcRdBCPgeU0rCQP3T8/ugROGZbo1biDJzx4iPyOTZcYt1 +3ns/DQw7V4x3D7k3B7jL3JhqY7xMjKo3ywXZQCYl1Rzyv7+AKrt9H2O7AxZf/TEc +MyGQN6zke7TkuuznO31rf1ECgYEAghw2I4vyx7pCR4Mw1Wrg/lQxpWx/58764LNE +Vfmi9Nw0zIkTBke0kLK8ALwmyxAziy0zkH/QMz+wTD4ascInT3dKZIOnaA9QvqBf +7GqoBJD3dthidUeFgVzE8iLCpcyKZD0mEq8Nj44BLxwZSnByz6tc4eAfCYgDWG0q +b6UHTukCgYEAol2Nv4sVOGMF799/O0rlqb3zDNTBUjeOiLPbhKqz1IFxT36eWfFO +kIkzp5ubqoHGO/ODZ+rH5H6ENxCp69mgd9cPp+Wf7NRkX94C5myYbSFQJTYJ5mh9 +Y0xlCu3V0sAcH9qrHqreBwInAvtWhwCcrURxSvCKfDScn3rAOtC2ePc= +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/config.yaml b/internal/otel_collector/receiver/jaegerreceiver/testdata/config.yaml new file mode 100644 index 00000000000..ca27938c2d4 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/config.yaml @@ -0,0 +1,66 @@ +receivers: + # The following demonstrates specifying different endpoints. + # The Jaeger receiver connects to ports on all available network interfaces. + # Ex: `endpoint: "9876"` is incorrect. + # Ex: `endpoint: "1.2.3.4:9876"` and ":9876" is correct. + jaeger/customname: + protocols: + grpc: + endpoint: "localhost:9876" + thrift_http: + endpoint: ":3456" + thrift_compact: + endpoint: "0.0.0.0:456" + queue_size: 100_000 + max_packet_size: 131_072 + workers: 100 + socket_buffer_size: 65_536 + thrift_binary: + endpoint: "0.0.0.0:789" + queue_size: 1_000 + max_packet_size: 65_536 + workers: 5 + socket_buffer_size: 0 + remote_sampling: + host_endpoint: "0.0.0.0:5778" + endpoint: "jaeger-collector:1234" + strategy_file: "/etc/strategies.json" + # The following demonstrates how to enable protocols with defaults. + jaeger/defaults: + protocols: + grpc: + thrift_http: + thrift_compact: + thrift_binary: + # The following demonstrates only enabling certain protocols with defaults/overrides. + jaeger/mixed: + protocols: + grpc: + endpoint: "localhost:9876" + thrift_compact: + # The following demonstrates specifying different endpoints. + # The Jaeger receiver connects to ports on all available network interfaces. + # Ex: `endpoint: "9876"` is incorrect. + # Ex: `endpoint: "1.2.3.4:9876"` and ":9876" is correct. + jaeger/tls: + protocols: + grpc: + tls_settings: + cert_file: /test.crt + key_file: /test.key + endpoint: "localhost:9876" + thrift_http: + endpoint: ":3456" + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [jaeger/defaults] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/server.crt b/internal/otel_collector/receiver/jaegerreceiver/testdata/server.crt new file mode 100644 index 00000000000..c9e8b95d39a --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJANACN0VTlMdcMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG +A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjAwOTIy +MDUyMjExWhcNMzAwOTIwMDUyMjExWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ +QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV +MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAur2w74htbzpU9gJW8Jmqso0r/0etrwSlijWYyxH9FX72vaXBV8ZYwPtX +NQq8vYiez9MBhjGF3MLa62Sc4ATtobKKdNPatZh47P/lS3ugWXse5mh6k9I1I1oQ +ikDvD1zwJmFEamQztU9WZ/VAKNHNf6nBNujfU0UMzJ4R1KG7bXR00+XA/oYyil0H +CV//1Y3FPdw2znj1ulwF7Odfcsfjo4mkMAl+Ep4aU/BkA9elV4hJqCOynNoHJ2zG +CPNYCi9dh3UpPfC8Lk2MqYvAIWIoJbqddaMoWJfNCCxIQ8uPYHIcK+Clqak+N1lM +ans/2NLNJg5QSOGN331QWkRAcBumXQIDAQABoy4wLDAUBgNVHREEDTALgglsb2Nh +bGhvc3QwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQDN +ogqQ5hcH3F0B0imuaYD2CgeosAswtjxvQvz4Z7R/WSzl+yMfkoC45r6CUBUOOP3N +lN1ZD7D7S46sEwRxqOZIwHMvXGVvBPuXuxZfP90t8J4H6IdxQfpzQC5SwNO+s9Pw +YVKA+2FqQp1xvulJVr5BNruMv6v6ZZYDXh3E0W5m9kXiVjU6gOI8AAjO9OfFTsDL +2WB2+jxJ7tVuoTq9+sBPvR9SI5RUk7+0SauZQ8Y/QXNlAJ10BoZcNSHABo3D7WR5 +m2Qu38Sh5fz3Ae37QS/o76JKPsruCwsBQ2kbduYma+0G6Z+fMxyPT7C6b3moHiMs +tdrNBEJM4vbiD5nk0WWt +-----END CERTIFICATE----- diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/server.key b/internal/otel_collector/receiver/jaegerreceiver/testdata/server.key new file mode 100644 index 00000000000..707f20e1113 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAur2w74htbzpU9gJW8Jmqso0r/0etrwSlijWYyxH9FX72vaXB +V8ZYwPtXNQq8vYiez9MBhjGF3MLa62Sc4ATtobKKdNPatZh47P/lS3ugWXse5mh6 +k9I1I1oQikDvD1zwJmFEamQztU9WZ/VAKNHNf6nBNujfU0UMzJ4R1KG7bXR00+XA +/oYyil0HCV//1Y3FPdw2znj1ulwF7Odfcsfjo4mkMAl+Ep4aU/BkA9elV4hJqCOy +nNoHJ2zGCPNYCi9dh3UpPfC8Lk2MqYvAIWIoJbqddaMoWJfNCCxIQ8uPYHIcK+Cl +qak+N1lMans/2NLNJg5QSOGN331QWkRAcBumXQIDAQABAoIBAGJqVFR49wu2l04r +v3v/0GlXY0eflBZ4AXJMeuES8umgRxN9xt6mhuH11Gj85qmQ1fm7P8NkWCMXgl6q +YM7jagVc3gFiIZKw75If7s0QM1rVO1y81VUQZLbj4XGrdRIIrfvcKT1U37d/P498 +PjsFDyNn6I8yvXfaHTu9VrJUJj4xSaeUDX6I033nljbWFzPAHdpIGouPN0u69X5X +c2Y3DSC5P5vraD0v1rhnIz/pmUZGkLSXbxA0k4be7tD4Ek32TV91pGHj8AbJyu0Q +JCGJHpi7EiD27j89USgQppGpDouq7AgxdwKMFJs/8mZY7DGvQE3FhgsMPW18x2eD +fWyhppUCgYEA7PWvkX+/HkgBeZ9qFa9gN/HOFuVj80UiiCTy0mKzUNaQr6p3E1Rg +irQMpUrhB7heW2bS72QRbBgP+1DMvAZTuFj6H8ss8wxrJa1MFCVVGSPC2+qjKxPQ +GtOZt4mJjQukklz5dMHQphp3tlbLoDQeu3KRpa7KQy3dnbX4jpEMNiMCgYEAyb7+ +r0Hk0qxXB76O0nRVjK+1aas9JWuYOKfF7pk/z9576evmkhTGIjNjQTOQnJH24KkY +lTYaabcxwrj9O+8o0MmgapW5clkr0cMtL2C/VDTWlNOfFZ2T9yfRa8qtkblhCZ5M +rGU5rJyZYcqFSkiGi5b/Mu6wPY32rWoJ8bd1OX8CgYAlSOM/OaKQ2TOiN3sxvk6d +fua2o5F+jbpJQccTY4Rji99oRKJH4FbwfDQhLg8Kb/Ao4Zz/Hfe/0mlxWd1dGIHD +1/xDVGzWMXYKj6IQ6W7ibcYTZHAYLx3nmrPtNS73ioVyoj5+KKD0AeYkQrP3mTvc +ssJIF6CrwWPFlQRvKlOJkQKBgQC+hoV3db9nxiIayjePQRZZ2sZ0mKcSY95KAwfG +ISxGX1Kew43of33uZqFhvhTgCGkiGg+BOqsPE1cHEjT1GRNuujuo7OVJvDTJ0I0n +pTKLj6rmukQO4dYPH6eDKNFqQawGrVyzopUpEms4E051rLCDu4Ie05PVTfCcLPxf +LmaieQKBgDpgiJ3YRHD9SeEn0FREHludu3IAg2Wh57+rEMu/ZjEOHbjAKOmuHrGH +L1kXIS2uVB2BmDQlu1dNtPk2rJMi2onrlbD92A7xF/NDIJiOKQ/27YWQTF0Sj80a +4DyhFAHHBnr2MqFhPrdgoG0UgCE8NMEyKNxQBCI2vfOOg252kxUy +-----END RSA PRIVATE KEY----- diff --git a/internal/otel_collector/receiver/jaegerreceiver/testdata/strategies.json b/internal/otel_collector/receiver/jaegerreceiver/testdata/strategies.json new file mode 100644 index 00000000000..4e8eff73f3f --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/testdata/strategies.json @@ -0,0 +1,30 @@ +{ + "service_strategies": [ + { + "service": "foo", + "type": "probabilistic", + "param": 0.8, + "operation_strategies": [ + { + "operation": "op1", + "type": "probabilistic", + "param": 0.2 + }, + { + "operation": "op2", + "type": "probabilistic", + "param": 0.4 + } + ] + }, + { + "service": "bar", + "type": "ratelimiting", + "param": 5 + } + ], + "default_strategy": { + "type": "probabilistic", + "param": 0.5 + } + } \ No newline at end of file diff --git a/internal/otel_collector/receiver/jaegerreceiver/trace_receiver.go b/internal/otel_collector/receiver/jaegerreceiver/trace_receiver.go new file mode 100644 index 00000000000..c88307b29ca --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/trace_receiver.go @@ -0,0 +1,505 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +import ( + "context" + "fmt" + "io/ioutil" + "mime" + "net" + "net/http" + "sync" + + apacheThrift "github.com/apache/thrift/lib/go/thrift" + "github.com/gorilla/mux" + "github.com/jaegertracing/jaeger/cmd/agent/app/configmanager" + jSamplingConfig "github.com/jaegertracing/jaeger/cmd/agent/app/configmanager/grpc" + "github.com/jaegertracing/jaeger/cmd/agent/app/httpserver" + "github.com/jaegertracing/jaeger/cmd/agent/app/processors" + "github.com/jaegertracing/jaeger/cmd/agent/app/servers" + "github.com/jaegertracing/jaeger/cmd/agent/app/servers/thriftudp" + "github.com/jaegertracing/jaeger/cmd/collector/app/handler" + collectorSampling "github.com/jaegertracing/jaeger/cmd/collector/app/sampling" + staticStrategyStore "github.com/jaegertracing/jaeger/plugin/sampling/strategystore/static" + "github.com/jaegertracing/jaeger/proto-gen/api_v2" + "github.com/jaegertracing/jaeger/thrift-gen/agent" + "github.com/jaegertracing/jaeger/thrift-gen/baggage" + "github.com/jaegertracing/jaeger/thrift-gen/jaeger" + "github.com/jaegertracing/jaeger/thrift-gen/sampling" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "github.com/uber/jaeger-lib/metrics" + "go.uber.org/zap" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/obsreport" + jaegertranslator "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +// configuration defines the behavior and the ports that +// the Jaeger receiver will use. +type configuration struct { + CollectorThriftPort int + CollectorHTTPPort int + CollectorGRPCPort int + CollectorGRPCOptions []grpc.ServerOption + + AgentCompactThriftPort int + AgentCompactThriftConfig ServerConfigUDP + AgentBinaryThriftPort int + AgentBinaryThriftConfig ServerConfigUDP + AgentHTTPPort int + RemoteSamplingClientSettings configgrpc.GRPCClientSettings + RemoteSamplingStrategyFile string +} + +// Receiver type is used to receive spans that were originally intended to be sent to Jaeger. +// This receiver is basically a Jaeger collector. +type jReceiver struct { + // mu protects the fields of this type + mu sync.Mutex + + nextConsumer consumer.TracesConsumer + instanceName string + + startOnce sync.Once + stopOnce sync.Once + + config *configuration + + grpc *grpc.Server + collectorServer *http.Server + + agentSamplingManager *jSamplingConfig.SamplingManager + agentProcessors []processors.Processor + agentServer *http.Server + + logger *zap.Logger +} + +const ( + agentTransportBinary = "udp_thrift_binary" + agentTransportCompact = "udp_thrift_compact" + collectorHTTPTransport = "collector_http" + grpcTransport = "grpc" + + thriftFormat = "thrift" + protobufFormat = "protobuf" +) + +var ( + acceptedThriftFormats = map[string]struct{}{ + "application/x-thrift": {}, + "application/vnd.apache.thrift.binary": {}, + } +) + +// newJaegerReceiver creates a TracesReceiver that receives traffic as a Jaeger collector, and +// also as a Jaeger agent. +func newJaegerReceiver( + instanceName string, + config *configuration, + nextConsumer consumer.TracesConsumer, + params component.ReceiverCreateParams, +) *jReceiver { + return &jReceiver{ + config: config, + nextConsumer: nextConsumer, + instanceName: instanceName, + logger: params.Logger, + } +} + +func (jr *jReceiver) agentCompactThriftAddr() string { + var port int + if jr.config != nil { + port = jr.config.AgentCompactThriftPort + } + return fmt.Sprintf(":%d", port) +} + +func (jr *jReceiver) agentCompactThriftEnabled() bool { + return jr.config != nil && jr.config.AgentCompactThriftPort > 0 +} + +func (jr *jReceiver) agentBinaryThriftAddr() string { + var port int + if jr.config != nil { + port = jr.config.AgentBinaryThriftPort + } + return fmt.Sprintf(":%d", port) +} + +func (jr *jReceiver) agentBinaryThriftEnabled() bool { + return jr.config != nil && jr.config.AgentBinaryThriftPort > 0 +} + +func (jr *jReceiver) agentHTTPAddr() string { + var port int + if jr.config != nil { + port = jr.config.AgentHTTPPort + } + return fmt.Sprintf(":%d", port) +} + +func (jr *jReceiver) agentHTTPEnabled() bool { + return jr.config != nil && jr.config.AgentHTTPPort > 0 +} + +func (jr *jReceiver) collectorGRPCAddr() string { + var port int + if jr.config != nil { + port = jr.config.CollectorGRPCPort + } + return fmt.Sprintf(":%d", port) +} + +func (jr *jReceiver) collectorGRPCEnabled() bool { + return jr.config != nil && jr.config.CollectorGRPCPort > 0 +} + +func (jr *jReceiver) collectorHTTPAddr() string { + var port int + if jr.config != nil { + port = jr.config.CollectorHTTPPort + } + return fmt.Sprintf(":%d", port) +} + +func (jr *jReceiver) collectorHTTPEnabled() bool { + return jr.config != nil && jr.config.CollectorHTTPPort > 0 +} + +func (jr *jReceiver) Start(_ context.Context, host component.Host) error { + jr.mu.Lock() + defer jr.mu.Unlock() + + var err = componenterror.ErrAlreadyStarted + jr.startOnce.Do(func() { + if err = jr.startAgent(host); err != nil && err != componenterror.ErrAlreadyStarted { + return + } + + if err = jr.startCollector(host); err != nil && err != componenterror.ErrAlreadyStarted { + return + } + + err = nil + }) + return err +} + +func (jr *jReceiver) Shutdown(context.Context) error { + var err = componenterror.ErrAlreadyStopped + jr.stopOnce.Do(func() { + jr.mu.Lock() + defer jr.mu.Unlock() + var errs []error + + if jr.agentServer != nil { + if aerr := jr.agentServer.Close(); aerr != nil { + errs = append(errs, aerr) + } + jr.agentServer = nil + } + for _, processor := range jr.agentProcessors { + processor.Stop() + } + + if jr.collectorServer != nil { + if cerr := jr.collectorServer.Close(); cerr != nil { + errs = append(errs, cerr) + } + jr.collectorServer = nil + } + if jr.grpc != nil { + jr.grpc.Stop() + jr.grpc = nil + } + err = componenterror.CombineErrors(errs) + }) + + return err +} + +func consumeTraces(ctx context.Context, batch *jaeger.Batch, consumer consumer.TracesConsumer) (int, error) { + if batch == nil { + return 0, nil + } + td := jaegertranslator.ThriftBatchToInternalTraces(batch) + return len(batch.Spans), consumer.ConsumeTraces(ctx, td) +} + +var _ agent.Agent = (*agentHandler)(nil) +var _ api_v2.CollectorServiceServer = (*jReceiver)(nil) +var _ configmanager.ClientConfigManager = (*jReceiver)(nil) + +type agentHandler struct { + name string + transport string + nextConsumer consumer.TracesConsumer +} + +// EmitZipkinBatch is unsupported agent's +func (h *agentHandler) EmitZipkinBatch(context.Context, []*zipkincore.Span) (err error) { + panic("unsupported receiver") +} + +// EmitBatch implements thrift-gen/agent/Agent and it forwards +// Jaeger spans received by the Jaeger agent processor. +func (h *agentHandler) EmitBatch(ctx context.Context, batch *jaeger.Batch) error { + ctx = obsreport.ReceiverContext(ctx, h.name, h.transport) + ctx = obsreport.StartTraceDataReceiveOp(ctx, h.name, h.transport) + + numSpans, err := consumeTraces(ctx, batch, h.nextConsumer) + obsreport.EndTraceDataReceiveOp(ctx, thriftFormat, numSpans, err) + return err +} + +func (jr *jReceiver) GetSamplingStrategy(ctx context.Context, serviceName string) (*sampling.SamplingStrategyResponse, error) { + return jr.agentSamplingManager.GetSamplingStrategy(ctx, serviceName) +} + +func (jr *jReceiver) GetBaggageRestrictions(ctx context.Context, serviceName string) ([]*baggage.BaggageRestriction, error) { + br, err := jr.agentSamplingManager.GetBaggageRestrictions(ctx, serviceName) + if err != nil { + // Baggage restrictions are not yet implemented - refer to - https://github.com/jaegertracing/jaeger/issues/373 + // As of today, GetBaggageRestrictions() always returns an error. + // However, we `return nil, nil` here in order to serve a valid `200 OK` response. + return nil, nil + } + return br, nil +} + +func (jr *jReceiver) PostSpans(ctx context.Context, r *api_v2.PostSpansRequest) (*api_v2.PostSpansResponse, error) { + if c, ok := client.FromGRPC(ctx); ok { + ctx = client.NewContext(ctx, c) + } + + ctx = obsreport.ReceiverContext(ctx, jr.instanceName, grpcTransport) + ctx = obsreport.StartTraceDataReceiveOp(ctx, jr.instanceName, grpcTransport) + + td := jaegertranslator.ProtoBatchToInternalTraces(r.GetBatch()) + + err := jr.nextConsumer.ConsumeTraces(ctx, td) + obsreport.EndTraceDataReceiveOp(ctx, protobufFormat, len(r.GetBatch().Spans), err) + if err != nil { + return nil, err + } + + return &api_v2.PostSpansResponse{}, nil +} + +func (jr *jReceiver) startAgent(_ component.Host) error { + if !jr.agentBinaryThriftEnabled() && !jr.agentCompactThriftEnabled() && !jr.agentHTTPEnabled() { + return nil + } + + if jr.agentBinaryThriftEnabled() { + h := &agentHandler{ + name: jr.instanceName, + transport: agentTransportBinary, + nextConsumer: jr.nextConsumer, + } + processor, err := jr.buildProcessor(jr.agentBinaryThriftAddr(), jr.config.AgentBinaryThriftConfig, apacheThrift.NewTBinaryProtocolFactoryDefault(), h) + if err != nil { + return err + } + jr.agentProcessors = append(jr.agentProcessors, processor) + } + + if jr.agentCompactThriftEnabled() { + h := &agentHandler{ + name: jr.instanceName, + transport: agentTransportCompact, + nextConsumer: jr.nextConsumer, + } + processor, err := jr.buildProcessor(jr.agentCompactThriftAddr(), jr.config.AgentCompactThriftConfig, apacheThrift.NewTCompactProtocolFactory(), h) + if err != nil { + return err + } + jr.agentProcessors = append(jr.agentProcessors, processor) + } + + for _, processor := range jr.agentProcessors { + go processor.Serve() + } + + // Start upstream grpc client before serving sampling endpoints over HTTP + if jr.config.RemoteSamplingClientSettings.Endpoint != "" { + grpcOpts, err := jr.config.RemoteSamplingClientSettings.ToDialOptions() + if err != nil { + jr.logger.Error("Error creating grpc dial options for remote sampling endpoint", zap.Error(err)) + return err + } + conn, err := grpc.Dial(jr.config.RemoteSamplingClientSettings.Endpoint, grpcOpts...) + if err != nil { + jr.logger.Error("Error creating grpc connection to jaeger remote sampling endpoint", zap.String("endpoint", jr.config.RemoteSamplingClientSettings.Endpoint)) + return err + } + + jr.agentSamplingManager = jSamplingConfig.NewConfigManager(conn) + } + + if jr.agentHTTPEnabled() { + jr.agentServer = httpserver.NewHTTPServer(jr.agentHTTPAddr(), jr, metrics.NullFactory) + + go func() { + if err := jr.agentServer.ListenAndServe(); err != nil { + jr.logger.Error("http server failure", zap.Error(err)) + } + }() + } + + return nil +} + +func (jr *jReceiver) buildProcessor(address string, cfg ServerConfigUDP, factory apacheThrift.TProtocolFactory, a agent.Agent) (processors.Processor, error) { + handler := agent.NewAgentProcessor(a) + transport, err := thriftudp.NewTUDPServerTransport(address) + if err != nil { + return nil, err + } + if cfg.SocketBufferSize > 0 { + if err = transport.SetSocketBufferSize(cfg.SocketBufferSize); err != nil { + return nil, err + } + } + server, err := servers.NewTBufferedServer(transport, cfg.QueueSize, cfg.MaxPacketSize, metrics.NullFactory) + if err != nil { + return nil, err + } + processor, err := processors.NewThriftProcessor(server, cfg.Workers, metrics.NullFactory, factory, handler, jr.logger) + if err != nil { + return nil, err + } + return processor, nil +} + +func (jr *jReceiver) decodeThriftHTTPBody(r *http.Request) (*jaeger.Batch, *httpError) { + bodyBytes, err := ioutil.ReadAll(r.Body) + r.Body.Close() + if err != nil { + return nil, &httpError{ + handler.UnableToReadBodyErrFormat, + http.StatusInternalServerError, + } + } + + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return nil, &httpError{ + fmt.Sprintf("Cannot parse content type: %v", err), + http.StatusBadRequest, + } + } + if _, ok := acceptedThriftFormats[contentType]; !ok { + return nil, &httpError{ + fmt.Sprintf("Unsupported content type: %v", contentType), + http.StatusBadRequest, + } + } + + tdes := apacheThrift.NewTDeserializer() + batch := &jaeger.Batch{} + if err = tdes.Read(batch, bodyBytes); err != nil { + return nil, &httpError{ + fmt.Sprintf(handler.UnableToReadBodyErrFormat, err), + http.StatusBadRequest, + } + } + return batch, nil +} + +// HandleThriftHTTPBatch implements Jaeger HTTP Thrift handler. +func (jr *jReceiver) HandleThriftHTTPBatch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if c, ok := client.FromHTTP(r); ok { + ctx = client.NewContext(ctx, c) + } + + ctx = obsreport.ReceiverContext(ctx, jr.instanceName, collectorHTTPTransport) + ctx = obsreport.StartTraceDataReceiveOp(ctx, jr.instanceName, collectorHTTPTransport) + + batch, hErr := jr.decodeThriftHTTPBody(r) + if hErr != nil { + http.Error(w, hErr.msg, hErr.statusCode) + obsreport.EndTraceDataReceiveOp(ctx, thriftFormat, 0, hErr) + return + } + + numSpans, err := consumeTraces(ctx, batch, jr.nextConsumer) + if err != nil { + http.Error(w, fmt.Sprintf("Cannot submit Jaeger batch: %v", err), http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusAccepted) + } + obsreport.EndTraceDataReceiveOp(ctx, thriftFormat, numSpans, err) +} + +func (jr *jReceiver) startCollector(host component.Host) error { + if !jr.collectorGRPCEnabled() && !jr.collectorHTTPEnabled() { + return nil + } + + if jr.collectorHTTPEnabled() { + // Now the collector that runs over HTTP + caddr := jr.collectorHTTPAddr() + cln, cerr := net.Listen("tcp", caddr) + if cerr != nil { + return fmt.Errorf("failed to bind to Collector address %q: %v", caddr, cerr) + } + + nr := mux.NewRouter() + nr.HandleFunc("/api/traces", jr.HandleThriftHTTPBatch).Methods(http.MethodPost) + jr.collectorServer = &http.Server{Handler: nr} + go func() { + _ = jr.collectorServer.Serve(cln) + }() + } + + if jr.collectorGRPCEnabled() { + jr.grpc = grpc.NewServer(jr.config.CollectorGRPCOptions...) + gaddr := jr.collectorGRPCAddr() + gln, gerr := net.Listen("tcp", gaddr) + if gerr != nil { + return fmt.Errorf("failed to bind to gRPC address %q: %v", gaddr, gerr) + } + + api_v2.RegisterCollectorServiceServer(jr.grpc, jr) + + // init and register sampling strategy store + ss, gerr := staticStrategyStore.NewStrategyStore(staticStrategyStore.Options{ + StrategiesFile: jr.config.RemoteSamplingStrategyFile, + }, jr.logger) + if gerr != nil { + return fmt.Errorf("failed to create collector strategy store: %v", gerr) + } + api_v2.RegisterSamplingManagerServer(jr.grpc, collectorSampling.NewGRPCHandler(ss)) + + go func() { + if err := jr.grpc.Serve(gln); err != nil { + host.ReportFatalError(err) + } + }() + } + + return nil +} diff --git a/internal/otel_collector/receiver/jaegerreceiver/trace_receiver_test.go b/internal/otel_collector/receiver/jaegerreceiver/trace_receiver_test.go new file mode 100644 index 00000000000..407a8d70c89 --- /dev/null +++ b/internal/otel_collector/receiver/jaegerreceiver/trace_receiver_test.go @@ -0,0 +1,589 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaegerreceiver + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "path" + "testing" + "time" + + "github.com/apache/thrift/lib/go/thrift" + collectorSampling "github.com/jaegertracing/jaeger/cmd/collector/app/sampling" + "github.com/jaegertracing/jaeger/model" + staticStrategyStore "github.com/jaegertracing/jaeger/plugin/sampling/strategystore/static" + "github.com/jaegertracing/jaeger/proto-gen/api_v2" + jaegerthrift "github.com/jaegertracing/jaeger/thrift-gen/jaeger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" + "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +const jaegerReceiver = "jaeger_receiver_test" + +func TestTraceSource(t *testing.T) { + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, &configuration{}, nil, params) + require.NotNil(t, jr) +} + +type traceConsumer struct { + cb func(context.Context, pdata.Traces) +} + +func (t traceConsumer) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + go t.cb(ctx, td) + return nil +} + +func jaegerBatchToHTTPBody(b *jaegerthrift.Batch) (*http.Request, error) { + body, err := thrift.NewTSerializer().Write(context.Background(), b) + if err != nil { + return nil, err + } + r := httptest.NewRequest("POST", "/api/traces", bytes.NewReader(body)) + r.Header.Add("content-type", "application/x-thrift") + return r, nil +} + +func TestThriftHTTPBodyDecode(t *testing.T) { + jr := jReceiver{} + batch := &jaegerthrift.Batch{ + Process: jaegerthrift.NewProcess(), + Spans: []*jaegerthrift.Span{jaegerthrift.NewSpan()}, + } + r, err := jaegerBatchToHTTPBody(batch) + require.NoError(t, err, "failed to prepare http body") + + gotBatch, hErr := jr.decodeThriftHTTPBody(r) + require.Nil(t, hErr, "failed to decode http body") + assert.Equal(t, batch, gotBatch) +} + +func TestClientIPDetection(t *testing.T) { + ch := make(chan context.Context) + jr := jReceiver{ + nextConsumer: traceConsumer{ + func(ctx context.Context, _ pdata.Traces) { + ch <- ctx + }, + }, + } + batch := &jaegerthrift.Batch{ + Process: jaegerthrift.NewProcess(), + Spans: []*jaegerthrift.Span{jaegerthrift.NewSpan()}, + } + r, err := jaegerBatchToHTTPBody(batch) + require.NoError(t, err) + + wantClient, ok := client.FromHTTP(r) + assert.True(t, ok) + jr.HandleThriftHTTPBatch(httptest.NewRecorder(), r) + + select { + case ctx := <-ch: + gotClient, ok := client.FromContext(ctx) + assert.True(t, ok, "must get client back from context") + assert.Equal(t, wantClient, gotClient) + break + case <-time.After(time.Second * 2): + t.Error("next consumer did not receive the batch") + } +} + +func TestReception(t *testing.T) { + port := testutil.GetAvailablePort(t) + // 1. Create the Jaeger receiver aka "server" + config := &configuration{ + CollectorHTTPPort: int(port), // that's the only one used by this test + } + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + + t.Log("Starting") + + require.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost())) + + t.Log("Start") + + // 2. Then send spans to the Jaeger receiver. + collectorAddr := fmt.Sprintf("http://localhost:%d/api/traces", port) + td := generateTraceData() + batches, err := jaeger.InternalTracesToJaegerProto(td) + require.NoError(t, err) + for _, batch := range batches { + require.NoError(t, sendToCollector(collectorAddr, modelToThrift(batch))) + } + + assert.NoError(t, err, "should not have failed to create the Jaeger OpenCensus exporter") + + gotTraces := sink.AllTraces() + assert.Equal(t, 1, len(gotTraces)) + + assert.EqualValues(t, td, gotTraces[0]) +} + +func TestPortsNotOpen(t *testing.T) { + // an empty config should result in no open ports + config := &configuration{} + + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + + require.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost())) + + // there is a race condition here that we're ignoring. + // this test may occasionally pass incorrectly, but it will not fail incorrectly + // TODO: consider adding a way for a receiver to asynchronously signal that is ready to receive spans to eliminate races/arbitrary waits + l, err := net.Listen("tcp", "localhost:14250") + assert.NoError(t, err, "should have been able to listen on 14250. jaeger receiver incorrectly started grpc") + if l != nil { + l.Close() + } + + l, err = net.Listen("tcp", "localhost:14268") + assert.NoError(t, err, "should have been able to listen on 14268. jaeger receiver incorrectly started thrift_http") + if l != nil { + l.Close() + } +} + +func TestGRPCReception(t *testing.T) { + // prepare + config := &configuration{ + CollectorGRPCPort: 14250, // that's the only one used by this test + } + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + + require.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost())) + + conn, err := grpc.Dial(fmt.Sprintf("0.0.0.0:%d", config.CollectorGRPCPort), grpc.WithInsecure()) + require.NoError(t, err) + defer conn.Close() + + cl := api_v2.NewCollectorServiceClient(conn) + + now := time.Unix(1542158650, 536343000).UTC() + d10min := 10 * time.Minute + d2sec := 2 * time.Second + nowPlus10min := now.Add(d10min) + nowPlus10min2sec := now.Add(d10min).Add(d2sec) + + // test + req := grpcFixture(now, d10min, d2sec) + resp, err := cl.PostSpans(context.Background(), req, grpc.WaitForReady(true)) + + // verify + assert.NoError(t, err, "should not have failed to post spans") + assert.NotNil(t, resp, "response should not have been nil") + + gotTraces := sink.AllTraces() + assert.Equal(t, 1, len(gotTraces)) + want := expectedTraceData(now, nowPlus10min, nowPlus10min2sec) + + assert.Len(t, req.Batch.Spans, want.SpanCount(), "got a conflicting amount of spans") + + assert.EqualValues(t, want, gotTraces[0]) +} + +func TestGRPCReceptionWithTLS(t *testing.T) { + // prepare + var grpcServerOptions []grpc.ServerOption + tlsCreds := configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: path.Join(".", "testdata", "server.crt"), + KeyFile: path.Join(".", "testdata", "server.key"), + }, + } + + tlsCfg, err := tlsCreds.LoadTLSConfig() + assert.NoError(t, err) + grpcServerOptions = append(grpcServerOptions, grpc.Creds(credentials.NewTLS(tlsCfg))) + + port := testutil.GetAvailablePort(t) + config := &configuration{ + CollectorGRPCPort: int(port), + CollectorGRPCOptions: grpcServerOptions, + } + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + + require.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost())) + + creds, err := credentials.NewClientTLSFromFile(path.Join(".", "testdata", "server.crt"), "localhost") + require.NoError(t, err) + conn, err := grpc.Dial(jr.collectorGRPCAddr(), grpc.WithTransportCredentials(creds)) + require.NoError(t, err) + defer conn.Close() + + cl := api_v2.NewCollectorServiceClient(conn) + + now := time.Now() + d10min := 10 * time.Minute + d2sec := 2 * time.Second + nowPlus10min := now.Add(d10min) + nowPlus10min2sec := now.Add(d10min).Add(d2sec) + + // test + req := grpcFixture(now, d10min, d2sec) + resp, err := cl.PostSpans(context.Background(), req, grpc.WaitForReady(true)) + + // verify + assert.NoError(t, err, "should not have failed to post spans") + assert.NotNil(t, resp, "response should not have been nil") + + gotTraces := sink.AllTraces() + assert.Equal(t, 1, len(gotTraces)) + want := expectedTraceData(now, nowPlus10min, nowPlus10min2sec) + + assert.Len(t, req.Batch.Spans, want.SpanCount(), "got a conflicting amount of spans") + assert.EqualValues(t, want, gotTraces[0]) +} + +func expectedTraceData(t1, t2, t3 time.Time) pdata.Traces { + traceID := pdata.NewTraceID( + [16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}) + parentSpanID := pdata.NewSpanID([8]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18}) + childSpanID := pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8}) + + traces := pdata.NewTraces() + traces.ResourceSpans().Resize(1) + rs := traces.ResourceSpans().At(0) + rs.Resource().Attributes().InsertString(conventions.AttributeServiceName, "issaTest") + rs.Resource().Attributes().InsertBool("bool", true) + rs.Resource().Attributes().InsertString("string", "yes") + rs.Resource().Attributes().InsertInt("int64", 10000000) + rs.InstrumentationLibrarySpans().Resize(1) + rs.InstrumentationLibrarySpans().At(0).Spans().Resize(2) + + span0 := rs.InstrumentationLibrarySpans().At(0).Spans().At(0) + span0.SetSpanID(childSpanID) + span0.SetParentSpanID(parentSpanID) + span0.SetTraceID(traceID) + span0.SetName("DBSearch") + span0.SetStartTime(pdata.TimestampUnixNano(uint64(t1.UnixNano()))) + span0.SetEndTime(pdata.TimestampUnixNano(uint64(t2.UnixNano()))) + span0.Status().SetCode(pdata.StatusCodeError) + span0.Status().SetMessage("Stale indices") + + span1 := rs.InstrumentationLibrarySpans().At(0).Spans().At(1) + span1.SetSpanID(parentSpanID) + span1.SetTraceID(traceID) + span1.SetName("ProxyFetch") + span1.SetStartTime(pdata.TimestampUnixNano(uint64(t2.UnixNano()))) + span1.SetEndTime(pdata.TimestampUnixNano(uint64(t3.UnixNano()))) + span1.Status().SetCode(pdata.StatusCodeError) + span1.Status().SetMessage("Frontend crash") + + return traces +} + +func grpcFixture(t1 time.Time, d1, d2 time.Duration) *api_v2.PostSpansRequest { + traceID := model.TraceID{} + traceID.Unmarshal([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}) + parentSpanID := model.NewSpanID(binary.BigEndian.Uint64([]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})) + childSpanID := model.NewSpanID(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})) + + return &api_v2.PostSpansRequest{ + Batch: model.Batch{ + Process: &model.Process{ + ServiceName: "issaTest", + Tags: []model.KeyValue{ + model.Bool("bool", true), + model.String("string", "yes"), + model.Int64("int64", 1e7), + }, + }, + Spans: []*model.Span{ + { + TraceID: traceID, + SpanID: childSpanID, + OperationName: "DBSearch", + StartTime: t1, + Duration: d1, + Tags: []model.KeyValue{ + model.String(tracetranslator.TagStatusMsg, "Stale indices"), + model.Int64(tracetranslator.TagStatusCode, int64(pdata.StatusCodeError)), + model.Bool("error", true), + }, + References: []model.SpanRef{ + { + TraceID: traceID, + SpanID: parentSpanID, + RefType: model.SpanRefType_CHILD_OF, + }, + }, + }, + { + TraceID: traceID, + SpanID: parentSpanID, + OperationName: "ProxyFetch", + StartTime: t1.Add(d1), + Duration: d2, + Tags: []model.KeyValue{ + model.String(tracetranslator.TagStatusMsg, "Frontend crash"), + model.Int64(tracetranslator.TagStatusCode, int64(pdata.StatusCodeError)), + model.Bool("error", true), + }, + }, + }, + }, + } +} + +func TestSampling(t *testing.T) { + port := testutil.GetAvailablePort(t) + config := &configuration{ + CollectorGRPCPort: int(port), + RemoteSamplingStrategyFile: "testdata/strategies.json", + } + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + + require.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost())) + t.Log("Start") + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", config.CollectorGRPCPort), grpc.WithInsecure()) + assert.NoError(t, err) + defer conn.Close() + + cl := api_v2.NewSamplingManagerClient(conn) + + expected := &api_v2.SamplingStrategyResponse{ + StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC, + ProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{ + SamplingRate: 0.8, + }, + OperationSampling: &api_v2.PerOperationSamplingStrategies{ + DefaultSamplingProbability: 0.8, + PerOperationStrategies: []*api_v2.OperationSamplingStrategy{ + { + Operation: "op1", + ProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{ + SamplingRate: 0.2, + }, + }, + { + Operation: "op2", + ProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{ + SamplingRate: 0.4, + }, + }, + }, + }, + } + + resp, err := cl.GetSamplingStrategy(context.Background(), &api_v2.SamplingStrategyParameters{ + ServiceName: "foo", + }) + assert.NoError(t, err) + assert.Equal(t, expected, resp) +} + +func TestSamplingFailsOnNotConfigured(t *testing.T) { + port := testutil.GetAvailablePort(t) + // prepare + config := &configuration{ + CollectorGRPCPort: int(port), + } + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + + require.NoError(t, jr.Start(context.Background(), componenttest.NewNopHost())) + t.Log("Start") + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", config.CollectorGRPCPort), grpc.WithInsecure()) + assert.NoError(t, err) + defer conn.Close() + + cl := api_v2.NewSamplingManagerClient(conn) + + response, err := cl.GetSamplingStrategy(context.Background(), &api_v2.SamplingStrategyParameters{ + ServiceName: "nothing", + }) + require.NoError(t, err) + assert.Equal(t, 0.001, response.GetProbabilisticSampling().GetSamplingRate()) +} + +func TestSamplingFailsOnBadFile(t *testing.T) { + port := testutil.GetAvailablePort(t) + // prepare + config := &configuration{ + CollectorGRPCPort: int(port), + RemoteSamplingStrategyFile: "does-not-exist", + } + sink := new(consumertest.TracesSink) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr := newJaegerReceiver(jaegerReceiver, config, sink, params) + defer jr.Shutdown(context.Background()) + assert.Error(t, jr.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestSamplingStrategiesMutualTLS(t *testing.T) { + caPath := path.Join(".", "testdata", "ca.crt") + serverCertPath := path.Join(".", "testdata", "server.crt") + serverKeyPath := path.Join(".", "testdata", "server.key") + clientCertPath := path.Join(".", "testdata", "client.crt") + clientKeyPath := path.Join(".", "testdata", "client.key") + + // start gRPC server that serves sampling strategies + tlsCfgOpts := configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: caPath, + CertFile: serverCertPath, + KeyFile: serverKeyPath, + }, + } + tlsCfg, err := tlsCfgOpts.LoadTLSConfig() + require.NoError(t, err) + server, serverAddr := initializeGRPCTestServer(t, func(s *grpc.Server) { + ss, serr := staticStrategyStore.NewStrategyStore(staticStrategyStore.Options{ + StrategiesFile: path.Join(".", "testdata", "strategies.json"), + }, zap.NewNop()) + require.NoError(t, serr) + api_v2.RegisterSamplingManagerServer(s, collectorSampling.NewGRPCHandler(ss)) + }, grpc.Creds(credentials.NewTLS(tlsCfg))) + defer server.GracefulStop() + + // Create sampling strategies receiver + port := testutil.GetAvailablePort(t) + require.NoError(t, err) + hostEndpoint := fmt.Sprintf("localhost:%d", port) + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.RemoteSampling = &RemoteSamplingConfig{ + GRPCClientSettings: configgrpc.GRPCClientSettings{ + TLSSetting: configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: caPath, + CertFile: clientCertPath, + KeyFile: clientKeyPath, + }, + Insecure: false, + ServerName: "localhost", + }, + Endpoint: serverAddr.String(), + }, + HostEndpoint: hostEndpoint, + } + // at least one protocol has to be enabled + thriftHTTPPort := testutil.GetAvailablePort(t) + require.NoError(t, err) + cfg.Protocols.ThriftHTTP = &confighttp.HTTPServerSettings{ + Endpoint: fmt.Sprintf("localhost:%d", thriftHTTPPort), + } + exp, err := factory.CreateTracesReceiver(context.Background(), component.ReceiverCreateParams{Logger: zap.NewNop()}, cfg, consumertest.NewTracesNop()) + require.NoError(t, err) + host := &componenttest.ErrorWaitingHost{} + err = exp.Start(context.Background(), host) + require.NoError(t, err) + defer exp.Shutdown(context.Background()) + _, err = host.WaitForFatalError(200 * time.Millisecond) + require.NoError(t, err) + + resp, err := http.Get(fmt.Sprintf("http://%s?service=bar", hostEndpoint)) + require.NoError(t, err) + bodyBytes, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + assert.Contains(t, "{\"strategyType\":1,\"rateLimitingSampling\":{\"maxTracesPerSecond\":5}}", string(bodyBytes)) +} + +func TestConsumeThriftTrace(t *testing.T) { + tests := []struct { + batch *jaegerthrift.Batch + numSpans int + }{ + { + batch: nil, + }, + { + batch: &jaegerthrift.Batch{Spans: []*jaegerthrift.Span{{}}}, + numSpans: 1, + }, + } + for _, test := range tests { + numSpans, err := consumeTraces(context.Background(), test.batch, consumertest.NewTracesNop()) + require.NoError(t, err) + assert.Equal(t, test.numSpans, numSpans) + } +} + +func sendToCollector(endpoint string, batch *jaegerthrift.Batch) error { + buf, err := thrift.NewTSerializer().Write(context.Background(), batch) + if err != nil { + return err + } + req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(buf)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-thrift") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("failed to upload traces; HTTP status code: %d", resp.StatusCode) + } + return nil +} diff --git a/internal/otel_collector/receiver/kafkareceiver/README.md b/internal/otel_collector/receiver/kafkareceiver/README.md new file mode 100644 index 00000000000..f371ce08df3 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/README.md @@ -0,0 +1,63 @@ +# Kafka Receiver + +Kafka receiver receives traces from Kafka. Message payload encoding is configurable. + +Supported pipeline types: traces + +## Getting Started + +The following settings are required: + +- `protocol_version` (no default): Kafka protocol version e.g. 2.0.0 + +The following settings can be optionally configured: + +- `brokers` (default = localhost:9092): The list of kafka brokers +- `topic` (default = otlp_spans): The name of the kafka topic to export to +- `encoding` (default = otlp_proto): The encoding of the payload sent to kafka. Available encodings: + - `otlp_proto`: the payload is deserialized to `ExportTraceServiceRequest`. + - `jaeger_proto`: the payload is deserialized to a single Jaeger proto `Span`. + - `jaeger_json`: the payload is deserialized to a single Jaeger JSON Span using `jsonpb`. + - `zipkin_proto`: the payload is deserialized into a list of Zipkin proto spans. + - `zipkin_json`: the payload is deserialized into a list of Zipkin V2 JSON spans. + - `zipkin_thrift`: the payload is deserialized into a list of Zipkin Thrift spans. +- `group_id` (default = otel-collector): The consumer group that receiver will be consuming messages from +- `client_id` (default = otel-collector): The consumer client ID that receiver will use +- `auth` + - `plain_text` + - `username`: The username to use. + - `password`: The password to use + - `tls` + - `ca_file`: path to the CA cert. For a client this verifies the server certificate. Should + only be used if `insecure` is set to true. + - `cert_file`: path to the TLS cert to use for TLS required connections. Should + only be used if `insecure` is set to true. + - `key_file`: path to the TLS key to use for TLS required connections. Should + only be used if `insecure` is set to true. + - `insecure` (default = false): Disable verifying the server's certificate + chain and host name (`InsecureSkipVerify` in the tls config) + - `server_name_override`: ServerName indicates the name of the server requested by the client + in order to support virtual hosting. + - `kerberos` + - `service_name`: Kerberos service name + - `realm`: Kerberos realm + - `use_keytab`: Use of keytab instead of password, if this is true, keytab file will be used instead of password + - `username`: The Kerberos username used for authenticate with KDC + - `password`: The Kerberos password used for authenticate with KDC + - `config_file`: Path to Kerberos configuration. i.e /etc/krb5.conf + - `keytab_file`: Path to keytab file. i.e /etc/security/kafka.keytab +- `metadata` + - `full` (default = true): Whether to maintain a full set of metadata. When + disabled the client does not make the initial request to broker at the + startup. + - `retry` + - `max` (default = 3): The number of retries to get metadata + - `backoff` (default = 250ms): How long to wait between metadata retries + +Example: + +```yaml +receivers: + kafka: + protocol_version: 2.0.0 +``` diff --git a/internal/otel_collector/receiver/kafkareceiver/config.go b/internal/otel_collector/receiver/kafkareceiver/config.go new file mode 100644 index 00000000000..fe03de1bb45 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/config.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/kafkaexporter" +) + +// Config defines configuration for Kafka receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` + // The list of kafka brokers (default localhost:9092) + Brokers []string `mapstructure:"brokers"` + // Kafka protocol version + ProtocolVersion string `mapstructure:"protocol_version"` + // The name of the kafka topic to consume from (default "otlp_spans") + Topic string `mapstructure:"topic"` + // Encoding of the messages (default "otlp_proto") + Encoding string `mapstructure:"encoding"` + // The consumer group that receiver will be consuming messages from (default "otel-collector") + GroupID string `mapstructure:"group_id"` + // The consumer client ID that receiver will use (default "otel-collector") + ClientID string `mapstructure:"client_id"` + + // Metadata is the namespace for metadata management properties used by the + // Client, and shared by the Producer/Consumer. + Metadata kafkaexporter.Metadata `mapstructure:"metadata"` + + Authentication kafkaexporter.Authentication `mapstructure:"auth"` +} diff --git a/internal/otel_collector/receiver/kafkareceiver/config_test.go b/internal/otel_collector/receiver/kafkareceiver/config_test.go new file mode 100644 index 00000000000..7e044ef42f2 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/config_test.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/kafkaexporter" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + require.NoError(t, err) + require.Equal(t, 1, len(cfg.Receivers)) + + r := cfg.Receivers[typeStr].(*Config) + assert.Equal(t, &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: typeStr, + TypeVal: typeStr, + }, + Topic: "spans", + Encoding: "otlp_proto", + Brokers: []string{"foo:123", "bar:456"}, + ClientID: "otel-collector", + GroupID: "otel-collector", + Authentication: kafkaexporter.Authentication{ + TLS: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "ca.pem", + CertFile: "cert.pem", + KeyFile: "key.pem", + }, + }, + }, + Metadata: kafkaexporter.Metadata{ + Full: true, + Retry: kafkaexporter.MetadataRetry{ + Max: 10, + Backoff: time.Second * 5, + }, + }, + }, r) +} diff --git a/internal/otel_collector/receiver/kafkareceiver/factory.go b/internal/otel_collector/receiver/kafkareceiver/factory.go new file mode 100644 index 00000000000..f6cd6ee65ad --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/factory.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/exporter/kafkaexporter" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +const ( + typeStr = "kafka" + defaultTopic = "otlp_spans" + defaultEncoding = "otlp_proto" + defaultBroker = "localhost:9092" + defaultClientID = "otel-collector" + defaultGroupID = defaultClientID + + // default from sarama.NewConfig() + defaultMetadataRetryMax = 3 + // default from sarama.NewConfig() + defaultMetadataRetryBackoff = time.Millisecond * 250 + // default from sarama.NewConfig() + defaultMetadataFull = true +) + +// FactoryOption applies changes to kafkaExporterFactory. +type FactoryOption func(factory *kafkaReceiverFactory) + +// WithAddUnmarshallers adds marshallers. +func WithAddUnmarshallers(encodingMarshaller map[string]Unmarshaller) FactoryOption { + return func(factory *kafkaReceiverFactory) { + for encoding, unmarshaller := range encodingMarshaller { + factory.unmarshalers[encoding] = unmarshaller + } + } +} + +// NewFactory creates Kafka receiver factory. +func NewFactory(options ...FactoryOption) component.ReceiverFactory { + f := &kafkaReceiverFactory{ + unmarshalers: defaultUnmarshallers(), + } + for _, o := range options { + o(f) + } + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithTraces(f.createTraceReceiver)) +} + +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Topic: defaultTopic, + Encoding: defaultEncoding, + Brokers: []string{defaultBroker}, + ClientID: defaultClientID, + GroupID: defaultGroupID, + Metadata: kafkaexporter.Metadata{ + Full: defaultMetadataFull, + Retry: kafkaexporter.MetadataRetry{ + Max: defaultMetadataRetryMax, + Backoff: defaultMetadataRetryBackoff, + }, + }, + } +} + +type kafkaReceiverFactory struct { + unmarshalers map[string]Unmarshaller +} + +func (f *kafkaReceiverFactory) createTraceReceiver( + _ context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer, +) (component.TracesReceiver, error) { + c := cfg.(*Config) + r, err := newReceiver(*c, params, f.unmarshalers, nextConsumer) + if err != nil { + return nil, err + } + return r, nil +} diff --git a/internal/otel_collector/receiver/kafkareceiver/factory_test.go b/internal/otel_collector/receiver/kafkareceiver/factory_test.go new file mode 100644 index 00000000000..4417776a9ef --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/factory_test.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig().(*Config) + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) + assert.Equal(t, []string{defaultBroker}, cfg.Brokers) + assert.Equal(t, defaultTopic, cfg.Topic) + assert.Equal(t, defaultGroupID, cfg.GroupID) + assert.Equal(t, defaultClientID, cfg.ClientID) +} + +func TestCreateTraceReceiver(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Brokers = []string{"invalid:9092"} + cfg.ProtocolVersion = "2.0.0" + f := kafkaReceiverFactory{unmarshalers: defaultUnmarshallers()} + r, err := f.createTraceReceiver(context.Background(), component.ReceiverCreateParams{}, cfg, nil) + // no available broker + require.Error(t, err) + assert.Nil(t, r) +} + +func TestCreateTraceReceiver_error(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.ProtocolVersion = "2.0.0" + // disable contacting broker at startup + cfg.Metadata.Full = false + f := kafkaReceiverFactory{unmarshalers: defaultUnmarshallers()} + r, err := f.createTraceReceiver(context.Background(), component.ReceiverCreateParams{}, cfg, nil) + require.NoError(t, err) + assert.NotNil(t, r) +} + +func TestWithUnmarshallers(t *testing.T) { + unmarshaller := &customUnamarshaller{} + f := NewFactory(WithAddUnmarshallers(map[string]Unmarshaller{unmarshaller.Encoding(): unmarshaller})) + cfg := createDefaultConfig().(*Config) + // disable contacting broker + cfg.Metadata.Full = false + cfg.ProtocolVersion = "2.0.0" + + t.Run("custom_encoding", func(t *testing.T) { + cfg.Encoding = unmarshaller.Encoding() + exporter, err := f.CreateTracesReceiver(context.Background(), component.ReceiverCreateParams{}, cfg, nil) + require.NoError(t, err) + require.NotNil(t, exporter) + }) + t.Run("default_encoding", func(t *testing.T) { + cfg.Encoding = new(otlpProtoUnmarshaller).Encoding() + exporter, err := f.CreateTracesReceiver(context.Background(), component.ReceiverCreateParams{}, cfg, nil) + require.NoError(t, err) + assert.NotNil(t, exporter) + }) +} + +type customUnamarshaller struct { +} + +var _ Unmarshaller = (*customUnamarshaller)(nil) + +func (c customUnamarshaller) Unmarshal(bytes []byte) (pdata.Traces, error) { + panic("implement me") +} + +func (c customUnamarshaller) Encoding() string { + return "custom" +} diff --git a/internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller.go b/internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller.go new file mode 100644 index 00000000000..579f7f6e1d5 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller.go @@ -0,0 +1,69 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "bytes" + + "github.com/gogo/protobuf/jsonpb" + jaegerproto "github.com/jaegertracing/jaeger/model" + + "go.opentelemetry.io/collector/consumer/pdata" + jaegertranslator "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +type jaegerProtoSpanUnmarshaller struct { +} + +var _ Unmarshaller = (*jaegerProtoSpanUnmarshaller)(nil) + +func (j jaegerProtoSpanUnmarshaller) Unmarshal(bytes []byte) (pdata.Traces, error) { + span := &jaegerproto.Span{} + err := span.Unmarshal(bytes) + if err != nil { + return pdata.NewTraces(), err + } + return jaegerSpanToTraces(span), nil +} + +func (j jaegerProtoSpanUnmarshaller) Encoding() string { + return "jaeger_proto" +} + +type jaegerJSONSpanUnmarshaller struct { +} + +var _ Unmarshaller = (*jaegerJSONSpanUnmarshaller)(nil) + +func (j jaegerJSONSpanUnmarshaller) Unmarshal(data []byte) (pdata.Traces, error) { + span := &jaegerproto.Span{} + err := jsonpb.Unmarshal(bytes.NewReader(data), span) + if err != nil { + return pdata.NewTraces(), err + } + return jaegerSpanToTraces(span), nil +} + +func (j jaegerJSONSpanUnmarshaller) Encoding() string { + return "jaeger_json" +} + +func jaegerSpanToTraces(span *jaegerproto.Span) pdata.Traces { + batch := jaegerproto.Batch{ + Spans: []*jaegerproto.Span{span}, + Process: span.Process, + } + return jaegertranslator.ProtoBatchToInternalTraces(batch) +} diff --git a/internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller_test.go b/internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller_test.go new file mode 100644 index 00000000000..274651cacbc --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/jaeger_unmarshaller_test.go @@ -0,0 +1,87 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "bytes" + "testing" + + "github.com/gogo/protobuf/jsonpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + jaegertranslator "go.opentelemetry.io/collector/translator/trace/jaeger" +) + +func TestUnmarshallJaeger(t *testing.T) { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetName("foo") + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetStartTime(pdata.TimestampUnixNano(10)) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetEndTime(pdata.TimestampUnixNano(20)) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetTraceID(pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetSpanID(pdata.NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + batches, err := jaegertranslator.InternalTracesToJaegerProto(td) + require.NoError(t, err) + + protoBytes, err := batches[0].Spans[0].Marshal() + require.NoError(t, err) + + jsonMarshaller := &jsonpb.Marshaler{} + jsonBytes := new(bytes.Buffer) + jsonMarshaller.Marshal(jsonBytes, batches[0].Spans[0]) + + tests := []struct { + unmarshaller Unmarshaller + encoding string + bytes []byte + }{ + { + unmarshaller: jaegerProtoSpanUnmarshaller{}, + encoding: "jaeger_proto", + bytes: protoBytes, + }, + { + unmarshaller: jaegerJSONSpanUnmarshaller{}, + encoding: "jaeger_json", + bytes: jsonBytes.Bytes(), + }, + } + for _, test := range tests { + t.Run(test.encoding, func(t *testing.T) { + got, err := test.unmarshaller.Unmarshal(test.bytes) + require.NoError(t, err) + assert.Equal(t, td, got) + assert.Equal(t, test.encoding, test.unmarshaller.Encoding()) + }) + } +} + +func TestUnmarshallJaegerProto_error(t *testing.T) { + p := jaegerProtoSpanUnmarshaller{} + got, err := p.Unmarshal([]byte("+$%")) + assert.Equal(t, pdata.NewTraces(), got) + assert.Error(t, err) +} + +func TestUnmarshallJaegerJSON_error(t *testing.T) { + p := jaegerJSONSpanUnmarshaller{} + got, err := p.Unmarshal([]byte("+$%")) + assert.Equal(t, pdata.NewTraces(), got) + assert.Error(t, err) +} diff --git a/internal/otel_collector/receiver/kafkareceiver/kafka_receiver.go b/internal/otel_collector/receiver/kafkareceiver/kafka_receiver.go new file mode 100644 index 00000000000..8703305c7ae --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/kafka_receiver.go @@ -0,0 +1,181 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "context" + "fmt" + "sync" + + "github.com/Shopify/sarama" + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/exporter/kafkaexporter" + "go.opentelemetry.io/collector/obsreport" +) + +const ( + transport = "kafka" +) + +var errUnrecognizedEncoding = fmt.Errorf("unrecognized encoding") + +// kafkaConsumer uses sarama to consume and handle messages from kafka. +type kafkaConsumer struct { + name string + consumerGroup sarama.ConsumerGroup + nextConsumer consumer.TracesConsumer + topics []string + cancelConsumeLoop context.CancelFunc + unmarshaller Unmarshaller + + logger *zap.Logger +} + +var _ component.Receiver = (*kafkaConsumer)(nil) + +func newReceiver(config Config, params component.ReceiverCreateParams, unmarshalers map[string]Unmarshaller, nextConsumer consumer.TracesConsumer) (*kafkaConsumer, error) { + unmarshaller := unmarshalers[config.Encoding] + if unmarshaller == nil { + return nil, errUnrecognizedEncoding + } + + c := sarama.NewConfig() + c.ClientID = config.ClientID + c.Metadata.Full = config.Metadata.Full + c.Metadata.Retry.Max = config.Metadata.Retry.Max + c.Metadata.Retry.Backoff = config.Metadata.Retry.Backoff + if config.ProtocolVersion != "" { + version, err := sarama.ParseKafkaVersion(config.ProtocolVersion) + if err != nil { + return nil, err + } + c.Version = version + } + if err := kafkaexporter.ConfigureAuthentication(config.Authentication, c); err != nil { + return nil, err + } + client, err := sarama.NewConsumerGroup(config.Brokers, config.GroupID, c) + if err != nil { + return nil, err + } + return &kafkaConsumer{ + name: config.Name(), + consumerGroup: client, + topics: []string{config.Topic}, + nextConsumer: nextConsumer, + unmarshaller: unmarshaller, + logger: params.Logger, + }, nil +} + +func (c *kafkaConsumer) Start(context.Context, component.Host) error { + ctx, cancel := context.WithCancel(context.Background()) + c.cancelConsumeLoop = cancel + consumerGroup := &consumerGroupHandler{ + name: c.name, + logger: c.logger, + unmarshaller: c.unmarshaller, + nextConsumer: c.nextConsumer, + ready: make(chan bool), + } + go c.consumeLoop(ctx, consumerGroup) + <-consumerGroup.ready + return nil +} + +func (c *kafkaConsumer) consumeLoop(ctx context.Context, handler sarama.ConsumerGroupHandler) error { + for { + // `Consume` should be called inside an infinite loop, when a + // server-side rebalance happens, the consumer session will need to be + // recreated to get the new claims + if err := c.consumerGroup.Consume(ctx, c.topics, handler); err != nil { + c.logger.Error("Error from consumer", zap.Error(err)) + } + // check if context was cancelled, signaling that the consumer should stop + if ctx.Err() != nil { + c.logger.Info("Consumer stopped", zap.Error(ctx.Err())) + return ctx.Err() + } + } +} + +func (c *kafkaConsumer) Shutdown(context.Context) error { + c.cancelConsumeLoop() + return c.consumerGroup.Close() +} + +type consumerGroupHandler struct { + name string + unmarshaller Unmarshaller + nextConsumer consumer.TracesConsumer + ready chan bool + readyCloser sync.Once + + logger *zap.Logger +} + +var _ sarama.ConsumerGroupHandler = (*consumerGroupHandler)(nil) + +func (c *consumerGroupHandler) Setup(session sarama.ConsumerGroupSession) error { + c.readyCloser.Do(func() { + close(c.ready) + }) + statsTags := []tag.Mutator{tag.Insert(tagInstanceName, c.name)} + _ = stats.RecordWithTags(session.Context(), statsTags, statPartitionStart.M(1)) + return nil +} + +func (c *consumerGroupHandler) Cleanup(session sarama.ConsumerGroupSession) error { + statsTags := []tag.Mutator{tag.Insert(tagInstanceName, c.name)} + _ = stats.RecordWithTags(session.Context(), statsTags, statPartitionClose.M(1)) + return nil +} + +func (c *consumerGroupHandler) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { + c.logger.Info("Starting consumer group", zap.Int32("partition", claim.Partition())) + for message := range claim.Messages() { + c.logger.Debug("Kafka message claimed", + zap.String("value", string(message.Value)), + zap.Time("timestamp", message.Timestamp), + zap.String("topic", message.Topic)) + session.MarkMessage(message, "") + + ctx := obsreport.ReceiverContext(session.Context(), c.name, transport) + ctx = obsreport.StartTraceDataReceiveOp(ctx, c.name, transport) + statsTags := []tag.Mutator{tag.Insert(tagInstanceName, c.name)} + _ = stats.RecordWithTags(ctx, statsTags, + statMessageCount.M(1), + statMessageOffset.M(message.Offset), + statMessageOffsetLag.M(claim.HighWaterMarkOffset()-message.Offset-1)) + + traces, err := c.unmarshaller.Unmarshal(message.Value) + if err != nil { + c.logger.Error("failed to unmarshall message", zap.Error(err)) + return err + } + + err = c.nextConsumer.ConsumeTraces(session.Context(), traces) + obsreport.EndTraceDataReceiveOp(ctx, c.unmarshaller.Encoding(), traces.SpanCount(), err) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/otel_collector/receiver/kafkareceiver/kafka_receiver_test.go b/internal/otel_collector/receiver/kafkareceiver/kafka_receiver_test.go new file mode 100644 index 00000000000..f865970186e --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/kafka_receiver_test.go @@ -0,0 +1,334 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/Shopify/sarama" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/stats/view" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/kafkaexporter" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" +) + +func TestNewReceiver_version_err(t *testing.T) { + c := Config{ + Encoding: defaultEncoding, + ProtocolVersion: "none", + } + r, err := newReceiver(c, component.ReceiverCreateParams{}, defaultUnmarshallers(), consumertest.NewTracesNop()) + assert.Error(t, err) + assert.Nil(t, r) +} + +func TestNewReceiver_encoding_err(t *testing.T) { + c := Config{ + Encoding: "foo", + } + r, err := newReceiver(c, component.ReceiverCreateParams{}, defaultUnmarshallers(), consumertest.NewTracesNop()) + require.Error(t, err) + assert.Nil(t, r) + assert.EqualError(t, err, errUnrecognizedEncoding.Error()) +} + +func TestNewExporter_err_auth_type(t *testing.T) { + c := Config{ + ProtocolVersion: "2.0.0", + Authentication: kafkaexporter.Authentication{ + TLS: &configtls.TLSClientSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/doesnotexist", + }, + }, + }, + Encoding: defaultEncoding, + Metadata: kafkaexporter.Metadata{ + Full: false, + }, + } + r, err := newReceiver(c, component.ReceiverCreateParams{}, defaultUnmarshallers(), consumertest.NewTracesNop()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to load TLS config") + assert.Nil(t, r) +} + +func TestReceiverStart(t *testing.T) { + testClient := testConsumerGroup{once: &sync.Once{}} + c := kafkaConsumer{ + nextConsumer: consumertest.NewTracesNop(), + logger: zap.NewNop(), + consumerGroup: testClient, + } + + err := c.Start(context.Background(), nil) + require.NoError(t, err) + c.Shutdown(context.Background()) +} + +func TestReceiverStartConsume(t *testing.T) { + testClient := testConsumerGroup{once: &sync.Once{}} + c := kafkaConsumer{ + nextConsumer: consumertest.NewTracesNop(), + logger: zap.NewNop(), + consumerGroup: testClient, + } + ctx, cancelFunc := context.WithCancel(context.Background()) + c.cancelConsumeLoop = cancelFunc + c.Shutdown(context.Background()) + err := c.consumeLoop(ctx, &consumerGroupHandler{ + ready: make(chan bool), + }) + assert.EqualError(t, err, context.Canceled.Error()) +} + +func TestReceiver_error(t *testing.T) { + zcore, logObserver := observer.New(zapcore.ErrorLevel) + logger := zap.New(zcore) + + expectedErr := fmt.Errorf("handler error") + testClient := testConsumerGroup{once: &sync.Once{}, err: expectedErr} + c := kafkaConsumer{ + nextConsumer: consumertest.NewTracesNop(), + logger: logger, + consumerGroup: testClient, + } + + err := c.Start(context.Background(), nil) + require.NoError(t, err) + c.Shutdown(context.Background()) + waitUntil(func() bool { + return logObserver.FilterField(zap.Error(expectedErr)).Len() > 0 + }, 100, time.Millisecond*100) + assert.True(t, logObserver.FilterField(zap.Error(expectedErr)).Len() > 0) +} + +func TestConsumerGroupHandler(t *testing.T) { + views := MetricViews() + view.Register(views...) + defer view.Unregister(views...) + + c := consumerGroupHandler{ + unmarshaller: &otlpProtoUnmarshaller{}, + logger: zap.NewNop(), + ready: make(chan bool), + nextConsumer: consumertest.NewTracesNop(), + } + + testSession := testConsumerGroupSession{} + err := c.Setup(testSession) + require.NoError(t, err) + _, ok := <-c.ready + assert.False(t, ok) + viewData, err := view.RetrieveData(statPartitionStart.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData := viewData[0].Data.(*view.SumData) + assert.Equal(t, float64(1), distData.Value) + + err = c.Cleanup(testSession) + require.NoError(t, err) + viewData, err = view.RetrieveData(statPartitionClose.Name()) + require.NoError(t, err) + assert.Equal(t, 1, len(viewData)) + distData = viewData[0].Data.(*view.SumData) + assert.Equal(t, float64(1), distData.Value) + + groupClaim := testConsumerGroupClaim{ + messageChan: make(chan *sarama.ConsumerMessage), + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + err = c.ConsumeClaim(testSession, groupClaim) + require.NoError(t, err) + wg.Done() + }() + + groupClaim.messageChan <- &sarama.ConsumerMessage{} + close(groupClaim.messageChan) + wg.Wait() +} + +func TestConsumerGroupHandler_error_unmarshall(t *testing.T) { + c := consumerGroupHandler{ + unmarshaller: &otlpProtoUnmarshaller{}, + logger: zap.NewNop(), + ready: make(chan bool), + nextConsumer: consumertest.NewTracesNop(), + } + + wg := sync.WaitGroup{} + wg.Add(1) + groupClaim := &testConsumerGroupClaim{ + messageChan: make(chan *sarama.ConsumerMessage), + } + go func() { + err := c.ConsumeClaim(testConsumerGroupSession{}, groupClaim) + require.Error(t, err) + wg.Done() + }() + groupClaim.messageChan <- &sarama.ConsumerMessage{Value: []byte("!@#")} + close(groupClaim.messageChan) + wg.Wait() +} + +func TestConsumerGroupHandler_error_nextConsumer(t *testing.T) { + nextConsumer := new(consumertest.TracesSink) + consumerError := fmt.Errorf("failed to consumer") + nextConsumer.SetConsumeError(consumerError) + c := consumerGroupHandler{ + unmarshaller: &otlpProtoUnmarshaller{}, + logger: zap.NewNop(), + ready: make(chan bool), + nextConsumer: nextConsumer, + } + + wg := sync.WaitGroup{} + wg.Add(1) + groupClaim := &testConsumerGroupClaim{ + messageChan: make(chan *sarama.ConsumerMessage), + } + go func() { + e := c.ConsumeClaim(testConsumerGroupSession{}, groupClaim) + assert.EqualError(t, e, consumerError.Error()) + wg.Done() + }() + + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + request := &otlptrace.ExportTraceServiceRequest{ + ResourceSpans: pdata.TracesToOtlp(td), + } + bts, err := request.Marshal() + require.NoError(t, err) + groupClaim.messageChan <- &sarama.ConsumerMessage{Value: bts} + close(groupClaim.messageChan) + wg.Wait() +} + +type testConsumerGroupClaim struct { + messageChan chan *sarama.ConsumerMessage +} + +var _ sarama.ConsumerGroupClaim = (*testConsumerGroupClaim)(nil) + +const ( + testTopic = "otlp_spans" + testPartition = 5 + testInitialOffset = 6 + testHighWatermarkOffset = 4 +) + +func (t testConsumerGroupClaim) Topic() string { + return testTopic +} + +func (t testConsumerGroupClaim) Partition() int32 { + return testPartition +} + +func (t testConsumerGroupClaim) InitialOffset() int64 { + return testInitialOffset +} + +func (t testConsumerGroupClaim) HighWaterMarkOffset() int64 { + return testHighWatermarkOffset +} + +func (t testConsumerGroupClaim) Messages() <-chan *sarama.ConsumerMessage { + return t.messageChan +} + +type testConsumerGroupSession struct { +} + +func (t testConsumerGroupSession) Commit() { + panic("implement me") +} + +var _ sarama.ConsumerGroupSession = (*testConsumerGroupSession)(nil) + +func (t testConsumerGroupSession) Claims() map[string][]int32 { + panic("implement me") +} + +func (t testConsumerGroupSession) MemberID() string { + panic("implement me") +} + +func (t testConsumerGroupSession) GenerationID() int32 { + panic("implement me") +} + +func (t testConsumerGroupSession) MarkOffset(topic string, partition int32, offset int64, metadata string) { + panic("implement me") +} + +func (t testConsumerGroupSession) ResetOffset(topic string, partition int32, offset int64, metadata string) { + panic("implement me") +} + +func (t testConsumerGroupSession) MarkMessage(msg *sarama.ConsumerMessage, metadata string) { +} + +func (t testConsumerGroupSession) Context() context.Context { + return context.Background() +} + +type testConsumerGroup struct { + once *sync.Once + err error +} + +var _ sarama.ConsumerGroup = (*testConsumerGroup)(nil) + +func (t testConsumerGroup) Consume(ctx context.Context, topics []string, handler sarama.ConsumerGroupHandler) error { + t.once.Do(func() { + handler.Setup(testConsumerGroupSession{}) + }) + return t.err +} + +func (t testConsumerGroup) Errors() <-chan error { + panic("implement me") +} + +func (t testConsumerGroup) Close() error { + return nil +} + +func waitUntil(f func() bool, iterations int, sleepInterval time.Duration) { + for i := 0; i < iterations; i++ { + if f() { + return + } + time.Sleep(sleepInterval) + } +} diff --git a/internal/otel_collector/receiver/kafkareceiver/metrics.go b/internal/otel_collector/receiver/kafkareceiver/metrics.go new file mode 100644 index 00000000000..41b0e960c8e --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/metrics.go @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" +) + +var ( + tagInstanceName, _ = tag.NewKey("name") + + statMessageCount = stats.Int64("kafka_receiver_messages", "Number of received messages", stats.UnitDimensionless) + statMessageOffset = stats.Int64("kafka_receiver_current_offset", "Current message offset", stats.UnitDimensionless) + statMessageOffsetLag = stats.Int64("kafka_receiver_offset_lag", "Current offset lag", stats.UnitDimensionless) + + statPartitionStart = stats.Int64("kafka_receiver_partition_start", "Number of started partitions", stats.UnitDimensionless) + statPartitionClose = stats.Int64("kafka_receiver_partition_close", "Number of finished partitions", stats.UnitDimensionless) +) + +// MetricViews return metric views for Kafka receiver. +func MetricViews() []*view.View { + tagKeys := []tag.Key{tagInstanceName} + + countMessages := &view.View{ + Name: statMessageCount.Name(), + Measure: statMessageCount, + Description: statMessageCount.Description(), + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + + lastValueOffset := &view.View{ + Name: statMessageOffset.Name(), + Measure: statMessageOffset, + Description: statMessageOffset.Description(), + TagKeys: tagKeys, + Aggregation: view.LastValue(), + } + + lastValueOffsetLag := &view.View{ + Name: statMessageOffsetLag.Name(), + Measure: statMessageOffsetLag, + Description: statMessageOffsetLag.Description(), + TagKeys: tagKeys, + Aggregation: view.LastValue(), + } + + countPartitionStart := &view.View{ + Name: statPartitionStart.Name(), + Measure: statPartitionStart, + Description: statPartitionStart.Description(), + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + + countPartitionClose := &view.View{ + Name: statPartitionClose.Name(), + Measure: statPartitionClose, + Description: statPartitionClose.Description(), + TagKeys: tagKeys, + Aggregation: view.Sum(), + } + + return []*view.View{ + countMessages, + lastValueOffset, + lastValueOffsetLag, + countPartitionStart, + countPartitionClose, + } +} diff --git a/internal/otel_collector/receiver/kafkareceiver/metrics_test.go b/internal/otel_collector/receiver/kafkareceiver/metrics_test.go new file mode 100644 index 00000000000..1d67d522cbc --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/metrics_test.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMetrics(t *testing.T) { + metricViews := MetricViews() + viewNames := []string{ + "kafka_receiver_messages", + "kafka_receiver_current_offset", + "kafka_receiver_offset_lag", + "kafka_receiver_partition_start", + "kafka_receiver_partition_close", + } + for i, viewName := range viewNames { + assert.Equal(t, viewName, metricViews[i].Name) + } +} diff --git a/internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller.go b/internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller.go new file mode 100644 index 00000000000..b73978606b2 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller.go @@ -0,0 +1,38 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" +) + +type otlpProtoUnmarshaller struct { +} + +var _ Unmarshaller = (*otlpProtoUnmarshaller)(nil) + +func (p *otlpProtoUnmarshaller) Unmarshal(bytes []byte) (pdata.Traces, error) { + request := &otlptrace.ExportTraceServiceRequest{} + err := request.Unmarshal(bytes) + if err != nil { + return pdata.NewTraces(), err + } + return pdata.TracesFromOtlp(request.GetResourceSpans()), nil +} + +func (*otlpProtoUnmarshaller) Encoding() string { + return defaultEncoding +} diff --git a/internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller_test.go b/internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller_test.go new file mode 100644 index 00000000000..f52d898e425 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/otlp_unmarshaller_test.go @@ -0,0 +1,49 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" +) + +func TestUnmarshallOTLP(t *testing.T) { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).Resource().Attributes().InsertString("foo", "bar") + request := &otlptrace.ExportTraceServiceRequest{ + ResourceSpans: pdata.TracesToOtlp(td), + } + expected, err := request.Marshal() + require.NoError(t, err) + + p := otlpProtoUnmarshaller{} + got, err := p.Unmarshal(expected) + require.NoError(t, err) + assert.Equal(t, td, got) + assert.Equal(t, "otlp_proto", p.Encoding()) +} + +func TestUnmarshallOTLP_error(t *testing.T) { + p := otlpProtoUnmarshaller{} + got, err := p.Unmarshal([]byte("+$%")) + assert.Equal(t, pdata.NewTraces(), got) + assert.Error(t, err) +} diff --git a/internal/otel_collector/receiver/kafkareceiver/testdata/config.yaml b/internal/otel_collector/receiver/kafkareceiver/testdata/config.yaml new file mode 100644 index 00000000000..c170cf713a1 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/testdata/config.yaml @@ -0,0 +1,30 @@ +receivers: + kafka: + topic: spans + brokers: + - "foo:123" + - "bar:456" + client_id: otel-collector + group_id: otel-collector + auth: + tls: + ca_file: ca.pem + cert_file: cert.pem + key_file: key.pem + metadata: + retry: + max: 10 + backoff: 5s + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [kafka] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/kafkareceiver/unmarshaller.go b/internal/otel_collector/receiver/kafkareceiver/unmarshaller.go new file mode 100644 index 00000000000..5a2972607c2 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/unmarshaller.go @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Unmarshaller deserializes the message body. +type Unmarshaller interface { + // Unmarshal deserializes the message body into traces. + Unmarshal([]byte) (pdata.Traces, error) + + // Encoding of the serialized messages. + Encoding() string +} + +// defaultUnmarshallers returns map of supported encodings with Unmarshaller. +func defaultUnmarshallers() map[string]Unmarshaller { + otlp := &otlpProtoUnmarshaller{} + jaegerProto := jaegerProtoSpanUnmarshaller{} + jaegerJSON := jaegerJSONSpanUnmarshaller{} + zipkinProto := zipkinProtoSpanUnmarshaller{} + zipkinJSON := zipkinJSONSpanUnmarshaller{} + zipkinThrift := zipkinThriftSpanUnmarshaller{} + return map[string]Unmarshaller{ + otlp.Encoding(): otlp, + jaegerProto.Encoding(): jaegerProto, + jaegerJSON.Encoding(): jaegerJSON, + zipkinProto.Encoding(): zipkinProto, + zipkinJSON.Encoding(): zipkinJSON, + zipkinThrift.Encoding(): zipkinThrift, + } +} diff --git a/internal/otel_collector/receiver/kafkareceiver/unmarshaller_test.go b/internal/otel_collector/receiver/kafkareceiver/unmarshaller_test.go new file mode 100644 index 00000000000..952eb9ced22 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/unmarshaller_test.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDefaultUnMarshaller(t *testing.T) { + expectedEncodings := []string{ + "otlp_proto", + "jaeger_proto", + "jaeger_json", + "zipkin_proto", + "zipkin_json", + "zipkin_thrift", + } + marshallers := defaultUnmarshallers() + assert.Equal(t, len(expectedEncodings), len(marshallers)) + for _, e := range expectedEncodings { + t.Run(e, func(t *testing.T) { + m, ok := marshallers[e] + require.True(t, ok) + assert.NotNil(t, m) + }) + } +} diff --git a/internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller.go b/internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller.go new file mode 100644 index 00000000000..698cffa97f5 --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller.go @@ -0,0 +1,106 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "encoding/json" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" + + "go.opentelemetry.io/collector/consumer/pdata" + zipkintranslator "go.opentelemetry.io/collector/translator/trace/zipkin" +) + +type zipkinProtoSpanUnmarshaller struct { +} + +var _ Unmarshaller = (*zipkinProtoSpanUnmarshaller)(nil) + +func (z zipkinProtoSpanUnmarshaller) Unmarshal(bytes []byte) (pdata.Traces, error) { + parseSpans, err := zipkin_proto3.ParseSpans(bytes, false) + if err != nil { + return pdata.NewTraces(), err + } + return zipkintranslator.V2SpansToInternalTraces(parseSpans, false) +} + +func (z zipkinProtoSpanUnmarshaller) Encoding() string { + return "zipkin_proto" +} + +type zipkinJSONSpanUnmarshaller struct { +} + +var _ Unmarshaller = (*zipkinJSONSpanUnmarshaller)(nil) + +func (z zipkinJSONSpanUnmarshaller) Unmarshal(bytes []byte) (pdata.Traces, error) { + var spans []*zipkinmodel.SpanModel + if err := json.Unmarshal(bytes, &spans); err != nil { + return pdata.NewTraces(), err + } + return zipkintranslator.V2SpansToInternalTraces(spans, false) +} + +func (z zipkinJSONSpanUnmarshaller) Encoding() string { + return "zipkin_json" +} + +type zipkinThriftSpanUnmarshaller struct { +} + +var _ Unmarshaller = (*zipkinThriftSpanUnmarshaller)(nil) + +func (z zipkinThriftSpanUnmarshaller) Unmarshal(bytes []byte) (pdata.Traces, error) { + spans, err := deserializeZipkinThrift(bytes) + if err != nil { + return pdata.NewTraces(), err + } + return zipkintranslator.V1ThriftBatchToInternalTraces(spans) + +} + +func (z zipkinThriftSpanUnmarshaller) Encoding() string { + return "zipkin_thrift" +} + +// deserializeThrift decodes Thrift bytes to a list of spans. +// This code comes from jaegertracing/jaeger, ideally we should have imported +// it but this was creating many conflicts so brought the code to here. +// https://github.com/jaegertracing/jaeger/blob/6bc0c122bfca8e737a747826ae60a22a306d7019/model/converter/thrift/zipkin/deserialize.go#L36 +func deserializeZipkinThrift(b []byte) ([]*zipkincore.Span, error) { + buffer := thrift.NewTMemoryBuffer() + buffer.Write(b) + + transport := thrift.NewTBinaryProtocolTransport(buffer) + _, size, err := transport.ReadListBegin() // Ignore the returned element type + if err != nil { + return nil, err + } + + // We don't depend on the size returned by ReadListBegin to preallocate the array because it + // sometimes returns a nil error on bad input and provides an unreasonably large int for size + var spans []*zipkincore.Span + for i := 0; i < size; i++ { + zs := &zipkincore.Span{} + if err = zs.Read(transport); err != nil { + return nil, err + } + spans = append(spans, zs) + } + return spans, nil +} diff --git a/internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller_test.go b/internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller_test.go new file mode 100644 index 00000000000..3a0251f8f4d --- /dev/null +++ b/internal/otel_collector/receiver/kafkareceiver/zipkin_unmarshaller_test.go @@ -0,0 +1,120 @@ +// Copyright 2020 The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafkareceiver + +import ( + "testing" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" + zipkinreporter "github.com/openzipkin/zipkin-go/reporter" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + zipkintranslator "go.opentelemetry.io/collector/translator/trace/zipkin" +) + +func TestUnmarshallZipkin(t *testing.T) { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).Resource().Attributes().InitFromMap( + map[string]pdata.AttributeValue{conventions.AttributeServiceName: pdata.NewAttributeValueString("my_service")}) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().Resize(1) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetName("foo") + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetStartTime(pdata.TimestampUnixNano(1597759000)) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetEndTime(pdata.TimestampUnixNano(1597769000)) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetTraceID(pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetSpanID(pdata.NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).SetParentSpanID(pdata.NewSpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 0})) + spans, err := zipkintranslator.InternalTracesToZipkinSpans(td) + require.NoError(t, err) + + serializer := zipkinreporter.JSONSerializer{} + jsonBytes, err := serializer.Serialize(spans) + require.NoError(t, err) + + tSpan := &zipkincore.Span{Name: "foo"} + thriftTransport := thrift.NewTMemoryBuffer() + protocolTransport := thrift.NewTBinaryProtocolTransport(thriftTransport) + require.NoError(t, protocolTransport.WriteListBegin(thrift.STRUCT, 1)) + err = tSpan.Write(protocolTransport) + require.NoError(t, err) + require.NoError(t, protocolTransport.WriteListEnd()) + + tdThrift, err := zipkintranslator.V1ThriftBatchToInternalTraces([]*zipkincore.Span{tSpan}) + require.NoError(t, err) + + protoBytes, err := new(zipkin_proto3.SpanSerializer).Serialize(spans) + require.NoError(t, err) + + tests := []struct { + unmarshaller Unmarshaller + encoding string + bytes []byte + expected pdata.Traces + }{ + { + unmarshaller: zipkinProtoSpanUnmarshaller{}, + encoding: "zipkin_proto", + bytes: protoBytes, + expected: td, + }, + { + unmarshaller: zipkinJSONSpanUnmarshaller{}, + encoding: "zipkin_json", + bytes: jsonBytes, + expected: td, + }, + { + unmarshaller: zipkinThriftSpanUnmarshaller{}, + encoding: "zipkin_thrift", + bytes: thriftTransport.Buffer.Bytes(), + expected: tdThrift, + }, + } + for _, test := range tests { + t.Run(test.encoding, func(t *testing.T) { + traces, err := test.unmarshaller.Unmarshal(test.bytes) + require.NoError(t, err) + assert.Equal(t, test.expected, traces) + assert.Equal(t, test.encoding, test.unmarshaller.Encoding()) + }) + } +} + +func TestUnmarshallZipkinThrift_error(t *testing.T) { + p := zipkinThriftSpanUnmarshaller{} + got, err := p.Unmarshal([]byte("+$%")) + assert.Equal(t, pdata.NewTraces(), got) + assert.Error(t, err) +} + +func TestUnmarshallZipkinJSON_error(t *testing.T) { + p := zipkinJSONSpanUnmarshaller{} + got, err := p.Unmarshal([]byte("+$%")) + assert.Equal(t, pdata.NewTraces(), got) + assert.Error(t, err) +} + +func TestUnmarshallZipkinProto_error(t *testing.T) { + p := zipkinProtoSpanUnmarshaller{} + got, err := p.Unmarshal([]byte("+$%")) + assert.Equal(t, pdata.NewTraces(), got) + assert.Error(t, err) +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/README.md b/internal/otel_collector/receiver/opencensusreceiver/README.md new file mode 100644 index 00000000000..d119506cbc6 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/README.md @@ -0,0 +1,53 @@ +# OpenCensus Receiver + +Receives data via gRPC or HTTP using [OpenCensus]( https://opencensus.io/) +format. + +Supported pipeline types: traces, metrics + +## Getting Started + +All that is required to enable the OpenCensus receiver is to include it in the +receiver definitions. + +```yaml +receivers: + opencensus: +``` + +The following settings are configurable: + +- `endpoint` (default = 0.0.0.0:55678): host:port to which the receiver is + going to receive data. The valid syntax is described at + https://github.com/grpc/grpc/blob/master/doc/naming.md. + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) including CORS +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) + +## Writing with HTTP/JSON + +The OpenCensus receiver can receive trace export calls via HTTP/JSON in +addition to gRPC. The HTTP/JSON address is the same as gRPC as the protocol is +recognized and processed accordingly. + +To write traces with HTTP/JSON, `POST` to `[address]/v1/trace`. The JSON message +format parallels the gRPC protobuf format, see this +[OpenApi spec for it](https://github.com/census-instrumentation/opencensus-proto/blob/master/gen-openapi/opencensus/proto/agent/trace/v1/trace_service.swagger.json). + +The HTTP/JSON endpoint can also optionally configure +[CORS](https://fetch.spec.whatwg.org/#cors-protocol), which is enabled by +specifying a list of allowed CORS origins in the `cors_allowed_origins` field: + +```yaml +receivers: + opencensus: + cors_allowed_origins: + - http://test.com + # Origins can have wildcards with *, use * by itself to match any origin. + - https://*.example.com +``` diff --git a/internal/otel_collector/receiver/opencensusreceiver/config.go b/internal/otel_collector/receiver/opencensusreceiver/config.go new file mode 100644 index 00000000000..52be610bd15 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/config.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusreceiver + +import ( + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for OpenCensus receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // Configures the receiver server protocol. + configgrpc.GRPCServerSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // CorsOrigins are the allowed CORS origins for HTTP/JSON requests to grpc-gateway adapter + // for the OpenCensus receiver. See github.com/rs/cors + // An empty list means that CORS is not enabled at all. A wildcard (*) can be + // used to match any origin or one or more characters of an origin. + CorsOrigins []string `mapstructure:"cors_allowed_origins"` +} + +func (rOpts *Config) buildOptions() ([]ocOption, error) { + var opts []ocOption + if len(rOpts.CorsOrigins) > 0 { + opts = append(opts, withCorsOrigins(rOpts.CorsOrigins)) + } + + grpcServerOptions, err := rOpts.GRPCServerSettings.ToServerOption() + if err != nil { + return nil, err + } + if len(grpcServerOptions) > 0 { + opts = append(opts, withGRPCServerOptions(grpcServerOptions...)) + } + + return opts, nil +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/config_test.go b/internal/otel_collector/receiver/opencensusreceiver/config_test.go new file mode 100644 index 00000000000..5f753b84baa --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/config_test.go @@ -0,0 +1,196 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusreceiver + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 7) + + r0 := cfg.Receivers["opencensus"] + assert.Equal(t, r0, factory.CreateDefaultConfig()) + + r1 := cfg.Receivers["opencensus/customname"].(*Config) + assert.Equal(t, r1, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "opencensus/customname", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:9090", + Transport: "tcp", + }, + ReadBufferSize: 512 * 1024, + }, + }) + + r2 := cfg.Receivers["opencensus/keepalive"].(*Config) + assert.Equal(t, r2, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "opencensus/keepalive", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:55678", + Transport: "tcp", + }, + ReadBufferSize: 512 * 1024, + Keepalive: &configgrpc.KeepaliveServerConfig{ + ServerParameters: &configgrpc.KeepaliveServerParameters{ + MaxConnectionIdle: 11 * time.Second, + MaxConnectionAge: 12 * time.Second, + MaxConnectionAgeGrace: 13 * time.Second, + Time: 30 * time.Second, + Timeout: 5 * time.Second, + }, + EnforcementPolicy: &configgrpc.KeepaliveEnforcementPolicy{ + MinTime: 10 * time.Second, + PermitWithoutStream: true, + }, + }, + }, + }) + + r3 := cfg.Receivers["opencensus/msg-size-conc-connect-max-idle"].(*Config) + assert.Equal(t, r3, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "opencensus/msg-size-conc-connect-max-idle", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:55678", + Transport: "tcp", + }, + MaxRecvMsgSizeMiB: 32, + MaxConcurrentStreams: 16, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + Keepalive: &configgrpc.KeepaliveServerConfig{ + ServerParameters: &configgrpc.KeepaliveServerParameters{ + MaxConnectionIdle: 10 * time.Second, + }, + }, + }, + }) + + // TODO(ccaraman): Once the config loader checks for the files existence, this test may fail and require + // use of fake cert/key for test purposes. + r4 := cfg.Receivers["opencensus/tlscredentials"].(*Config) + assert.Equal(t, r4, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "opencensus/tlscredentials", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:55678", + Transport: "tcp", + }, + ReadBufferSize: 512 * 1024, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "test.crt", + KeyFile: "test.key", + }, + }, + }, + }) + + r5 := cfg.Receivers["opencensus/cors"].(*Config) + assert.Equal(t, r5, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "opencensus/cors", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:55678", + Transport: "tcp", + }, + ReadBufferSize: 512 * 1024, + }, + CorsOrigins: []string{"https://*.test.com", "https://test.com"}, + }) + + r6 := cfg.Receivers["opencensus/uds"].(*Config) + assert.Equal(t, r6, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "opencensus/uds", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "/tmp/opencensus.sock", + Transport: "unix", + }, + ReadBufferSize: 512 * 1024, + }, + }) +} + +func TestBuildOptions_TLSCredentials(t *testing.T) { + cfg := Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: "IncorrectTLS", + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "willfail", + }, + }, + }, + } + _, err := cfg.buildOptions() + assert.EqualError(t, err, `failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither`) + + cfg.TLSSetting = &configtls.TLSServerSetting{} + opt, err := cfg.buildOptions() + assert.NoError(t, err) + assert.NotNil(t, opt) +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/factory.go b/internal/otel_collector/receiver/opencensusreceiver/factory.go new file mode 100644 index 00000000000..484e37dcbc4 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/factory.go @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusreceiver + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "opencensus" +) + +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithTraces(createTraceReceiver), + receiverhelper.WithMetrics(createMetricsReceiver)) +} + +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:55678", + Transport: "tcp", + }, + // We almost write 0 bytes, so no need to tune WriteBufferSize. + ReadBufferSize: 512 * 1024, + }, + } +} + +func createTraceReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer, +) (component.TracesReceiver, error) { + r, err := createReceiver(cfg) + if err != nil { + return nil, err + } + + r.traceConsumer = nextConsumer + + return r, nil +} + +func createMetricsReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsReceiver, error) { + r, err := createReceiver(cfg) + if err != nil { + return nil, err + } + + r.metricsConsumer = nextConsumer + + return r, nil +} + +func createReceiver(cfg configmodels.Receiver) (*ocReceiver, error) { + rCfg := cfg.(*Config) + + // There must be one receiver for both metrics and traces. We maintain a map of + // receivers per config. + + // Check to see if there is already a receiver for this config. + receiver, ok := receivers[rCfg] + if !ok { + // Build the configuration options. + opts, err := rCfg.buildOptions() + if err != nil { + return nil, err + } + + // We don't have a receiver, so create one. + receiver, err = newOpenCensusReceiver( + rCfg.Name(), rCfg.NetAddr.Transport, rCfg.NetAddr.Endpoint, nil, nil, opts...) + if err != nil { + return nil, err + } + // Remember the receiver in the map + receivers[rCfg] = receiver + } + return receiver, nil +} + +// This is the map of already created OpenCensus receivers for particular configurations. +// We maintain this map because the Factory is asked trace and metric receivers separately +// when it gets CreateTracesReceiver() and CreateMetricsReceiver() but they must not +// create separate objects, they must use one ocReceiver object per configuration. +var receivers = map[*Config]*ocReceiver{} diff --git a/internal/otel_collector/receiver/opencensusreceiver/factory_test.go b/internal/otel_collector/receiver/opencensusreceiver/factory_test.go new file mode 100644 index 00000000000..fe70a32e447 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/factory_test.go @@ -0,0 +1,201 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusreceiver + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/testutil" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + cfg := createDefaultConfig() + + config := cfg.(*Config) + config.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + tReceiver, err := createTraceReceiver(context.Background(), params, cfg, nil) + assert.NotNil(t, tReceiver) + assert.NoError(t, err) + + mReceiver, err := createMetricsReceiver(context.Background(), params, cfg, nil) + assert.NotNil(t, mReceiver) + assert.NoError(t, err) +} + +func TestCreateTraceReceiver(t *testing.T) { + defaultReceiverSettings := configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + defaultNetAddr := confignet.NetAddr{ + Endpoint: testutil.GetAvailableLocalAddress(t), + Transport: "tcp", + } + defaultGRPCSettings := configgrpc.GRPCServerSettings{ + NetAddr: defaultNetAddr, + } + tests := []struct { + name string + cfg *Config + wantErr bool + }{ + { + name: "default", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + GRPCServerSettings: defaultGRPCSettings, + }, + }, + { + name: "invalid_port", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:112233", + Transport: "tcp", + }, + }, + }, + wantErr: true, + }, + { + name: "max-msg-size-and-concurrent-connections", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: defaultNetAddr, + MaxRecvMsgSizeMiB: 32, + MaxConcurrentStreams: 16, + }, + }, + }, + } + ctx := context.Background() + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr, err := createTraceReceiver(ctx, params, tt.cfg, consumertest.NewTracesNop()) + if (err != nil) != tt.wantErr { + t.Errorf("factory.CreateTracesReceiver() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tr != nil { + require.NoError(t, tr.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, tr.Shutdown(context.Background())) + } + }) + } +} + +func TestCreateMetricReceiver(t *testing.T) { + defaultReceiverSettings := configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + defaultNetAddr := confignet.NetAddr{ + Endpoint: testutil.GetAvailableLocalAddress(t), + Transport: "tcp", + } + defaultGRPCSettings := configgrpc.GRPCServerSettings{ + NetAddr: defaultNetAddr, + } + + tests := []struct { + name string + cfg *Config + wantErr bool + }{ + { + name: "default", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + GRPCServerSettings: defaultGRPCSettings, + }, + }, + { + name: "invalid_address", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "327.0.0.1:1122", + Transport: "tcp", + }, + }, + }, + wantErr: true, + }, + { + name: "keepalive", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: defaultNetAddr, + Keepalive: &configgrpc.KeepaliveServerConfig{ + ServerParameters: &configgrpc.KeepaliveServerParameters{ + MaxConnectionAge: 60 * time.Second, + }, + EnforcementPolicy: &configgrpc.KeepaliveEnforcementPolicy{ + MinTime: 30 * time.Second, + PermitWithoutStream: true, + }, + }, + }, + }, + }, + } + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc, err := createMetricsReceiver(context.Background(), params, tt.cfg, consumertest.NewMetricsNop()) + if (err != nil) != tt.wantErr { + t.Errorf("factory.CreateMetricsReceiver() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tc != nil { + require.NoError(t, tc.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, tc.Shutdown(context.Background())) + } + }) + } +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/doc.go b/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/doc.go new file mode 100644 index 00000000000..6d1d12e3c97 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package ocmetrics is the logic for receiving OpenCensus metrics proto from +// already instrumented applications and then passing them onto a metricsink instance. +package ocmetrics diff --git a/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus.go b/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus.go new file mode 100644 index 00000000000..d3023424451 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus.go @@ -0,0 +1,159 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ocmetrics + +import ( + "context" + "errors" + "io" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/translator/internaldata" +) + +// Receiver is the type used to handle metrics from OpenCensus exporters. +type Receiver struct { + agentmetricspb.UnimplementedMetricsServiceServer + instanceName string + nextConsumer consumer.MetricsConsumer +} + +// New creates a new ocmetrics.Receiver reference. +func New(instanceName string, nextConsumer consumer.MetricsConsumer) (*Receiver, error) { + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + ocr := &Receiver{ + instanceName: instanceName, + nextConsumer: nextConsumer, + } + return ocr, nil +} + +var _ agentmetricspb.MetricsServiceServer = (*Receiver)(nil) + +var errMetricsExportProtocolViolation = errors.New("protocol violation: Export's first message must have a Node") + +const ( + receiverTagValue = "oc_metrics" + receiverTransport = "grpc" // TODO: transport is being hard coded for now, investigate if info is available on context. + receiverDataFormat = "protobuf" +) + +// Export is the gRPC method that receives streamed metrics from +// OpenCensus-metricproto compatible libraries/applications. +func (ocr *Receiver) Export(mes agentmetricspb.MetricsService_ExportServer) error { + longLivedRPCCtx := obsreport.ReceiverContext(mes.Context(), ocr.instanceName, receiverTransport) + + // Retrieve the first message. It MUST have a non-nil Node. + recv, err := mes.Recv() + if err != nil { + return err + } + + // Check the condition that the first message has a non-nil Node. + if recv.Node == nil { + return errMetricsExportProtocolViolation + } + + var lastNonNilNode *commonpb.Node + var resource *resourcepb.Resource + // Now that we've got the first message with a Node, we can start to receive streamed up metrics. + for { + lastNonNilNode, resource, err = ocr.processReceivedMsg( + longLivedRPCCtx, + lastNonNilNode, + resource, + recv) + if err != nil { + return err + } + + recv, err = mes.Recv() + if err != nil { + if err == io.EOF { + // Do not return EOF as an error so that grpc-gateway calls get an empty + // response with HTTP status code 200 rather than a 500 error with EOF. + return nil + } + return err + } + } +} + +func (ocr *Receiver) processReceivedMsg( + longLivedRPCCtx context.Context, + lastNonNilNode *commonpb.Node, + resource *resourcepb.Resource, + recv *agentmetricspb.ExportMetricsServiceRequest, +) (*commonpb.Node, *resourcepb.Resource, error) { + // If a Node has been sent from downstream, save and use it. + if recv.Node != nil { + lastNonNilNode = recv.Node + } + + // TODO(songya): differentiate between unset and nil resource. See + // https://github.com/census-instrumentation/opencensus-proto/issues/146. + if recv.Resource != nil { + resource = recv.Resource + } + + md := consumerdata.MetricsData{ + Node: lastNonNilNode, + Resource: resource, + Metrics: recv.Metrics, + } + + err := ocr.sendToNextConsumer(longLivedRPCCtx, md) + return lastNonNilNode, resource, err +} + +func (ocr *Receiver) sendToNextConsumer(longLivedRPCCtx context.Context, md consumerdata.MetricsData) error { + ctx := obsreport.StartMetricsReceiveOp( + longLivedRPCCtx, + ocr.instanceName, + receiverTransport, + obsreport.WithLongLivedCtx()) + + numTimeSeries := 0 + numPoints := 0 + // Count number of time series and data points. + for _, metric := range md.Metrics { + numTimeSeries += len(metric.Timeseries) + for _, ts := range metric.GetTimeseries() { + numPoints += len(ts.GetPoints()) + } + } + + var consumerErr error + if len(md.Metrics) > 0 { + consumerErr = ocr.nextConsumer.ConsumeMetrics(ctx, internaldata.OCToMetrics(md)) + } + + obsreport.EndMetricsReceiveOp( + ctx, + receiverDataFormat, + numPoints, + consumerErr) + + return consumerErr +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus_test.go b/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus_test.go new file mode 100644 index 00000000000..11c7c7f0283 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/ocmetrics/opencensus_test.go @@ -0,0 +1,418 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ocmetrics + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "strings" + "sync" + "testing" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1" + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/exporter/opencensusexporter" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/internaldata" +) + +func TestReceiver_endToEnd(t *testing.T) { + metricSink := new(consumertest.MetricsSink) + + port, doneFn := ocReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + address := fmt.Sprintf("localhost:%d", port) + expFactory := opencensusexporter.NewFactory() + expCfg := expFactory.CreateDefaultConfig().(*opencensusexporter.Config) + expCfg.GRPCClientSettings.TLSSetting.Insecure = true + expCfg.Endpoint = address + expCfg.WaitForReady = true + oce, err := expFactory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, expCfg) + require.NoError(t, err) + err = oce.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err) + + defer func() { + require.NoError(t, oce.Shutdown(context.Background())) + }() + + md := testdata.GenerateMetricsOneMetric() + assert.NoError(t, oce.ConsumeMetrics(context.Background(), md)) + + testutil.WaitFor(t, func() bool { + return len(metricSink.AllMetrics()) != 0 + }) + gotMetrics := metricSink.AllMetrics() + require.Len(t, gotMetrics, 1) + assert.Equal(t, md, gotMetrics[0]) +} + +// Issue #43. Export should support node multiplexing. +// The goal is to ensure that Receiver can always support +// a passthrough mode where it initiates Export normally by firstly +// receiving the initiator node. However ti should still be able to +// accept nodes from downstream sources, but if a node isn't specified in +// an exportMetrics request, assume it is from the last received and non-nil node. +func TestExportMultiplexing(t *testing.T) { + metricSink := new(consumertest.MetricsSink) + + port, doneFn := ocReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + metricsClient, metricsClientDoneFn, err := makeMetricsServiceClient(port) + require.NoError(t, err, "Failed to create the gRPC MetricsService_ExportClient: %v", err) + defer metricsClientDoneFn() + + // Step 1) The initiation. + initiatingNode := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{ + Pid: 1, + HostName: "multiplexer", + }, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_JAVA}, + } + + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: initiatingNode}) + require.NoError(t, err, "Failed to send the initiating message: %v", err) + + // Step 1a) Send some metrics without a node, they should be registered as coming from the initiating node. + mLi := []*metricspb.Metric{makeMetric(1)} + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: nil, Metrics: mLi}) + require.NoError(t, err, "Failed to send the proxied message from app1: %v", err) + + // Step 2) Send a "proxied" metrics message from app1 with "node1" + node1 := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 9489, HostName: "nodejs-host"}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_NODE_JS}, + } + mL1 := []*metricspb.Metric{makeMetric(2)} + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: node1, Metrics: mL1}) + require.NoError(t, err, "Failed to send the proxied message from app1: %v", err) + + // Step 3) Send a metrics message without a node but with metrics: this + // should be registered as belonging to the last used node i.e. "node1". + mLn1 := []*metricspb.Metric{makeMetric(3)} + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: nil, Metrics: mLn1}) + require.NoError(t, err, "Failed to send the proxied message without a node: %v", err) + + // Step 4) Send a metrics message from a differently proxied node "node2" from app2 + node2 := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 7752, HostName: "golang-host"}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_GO_LANG}, + } + mL2 := []*metricspb.Metric{makeMetric(4)} + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: node2, Metrics: mL2}) + require.NoError(t, err, "Failed to send the proxied message from app2: %v", err) + + // Step 5a) Send a metrics message without a node but with metrics: this + // should be registered as belonging to the last used node i.e. "node2". + mLn2a := []*metricspb.Metric{makeMetric(5)} + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: nil, Metrics: mLn2a}) + require.NoError(t, err, "Failed to send the proxied message without a node: %v", err) + + // Step 5b) + mLn2b := []*metricspb.Metric{makeMetric(6)} + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: nil, Metrics: mLn2b}) + require.NoError(t, err, "Failed to send the proxied message without a node: %v", err) + // Give the process sometime to send data over the wire and perform batching + <-time.After(150 * time.Millisecond) + + // Examination time! + resultsMapping := make(map[string][]*metricspb.Metric) + for _, md := range metricSink.AllMetrics() { + ocmds := internaldata.MetricsToOC(md) + for _, ocmd := range ocmds { + resultsMapping[nodeToKey(ocmd.Node)] = append(resultsMapping[nodeToKey(ocmd.Node)], ocmd.Metrics...) + } + } + + // First things first, we expect exactly 3 unique keys + // 1. Initiating Node + // 2. Node 1 + // 3. Node 2 + if g, w := len(resultsMapping), 3; g != w { + t.Errorf("Got %d keys in the results map; Wanted exactly %d\n\nResultsMapping: %+v\n", g, w, resultsMapping) + } + + // Want metric counts + wantMetricCounts := map[string]int{ + nodeToKey(initiatingNode): 1, + nodeToKey(node1): 2, + nodeToKey(node2): 3, + } + for key, wantMetricCounts := range wantMetricCounts { + gotMetricCounts := len(resultsMapping[key]) + if gotMetricCounts != wantMetricCounts { + t.Errorf("Key=%q gotMetricCounts %d wantMetricCounts %d", key, gotMetricCounts, wantMetricCounts) + } + } + + // Now ensure that the exported metrics match up exactly with + // the nodes and the last seen node expectation/behavior. + // (or at least their serialized equivalents match up) + wantContents := map[string][]*metricspb.Metric{ + nodeToKey(initiatingNode): mLi, + nodeToKey(node1): append(mL1, mLn1...), + nodeToKey(node2): append(mL2, append(mLn2a, mLn2b...)...), + } + + gotBlob, _ := json.Marshal(resultsMapping) + wantBlob, _ := json.Marshal(wantContents) + if !bytes.Equal(gotBlob, wantBlob) { + t.Errorf("Unequal serialization results\nGot:\n\t%s\nWant:\n\t%s\n", gotBlob, wantBlob) + } +} + +// The first message without a Node MUST be rejected and teardown the connection. +// See https://github.com/census-instrumentation/opencensus-service/issues/53 +func TestExportProtocolViolations_nodelessFirstMessage(t *testing.T) { + metricSink := new(consumertest.MetricsSink) + + port, doneFn := ocReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + metricsClient, metricsClientDoneFn, err := makeMetricsServiceClient(port) + require.NoError(t, err, "Failed to create the gRPC MetricsService_ExportClient: %v", err) + defer metricsClientDoneFn() + + // Send a Nodeless first message + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: nil}) + require.NoError(t, err, "Unexpectedly failed to send the first message: %v", err) + + longDuration := 2 * time.Second + testDone := make(chan bool, 1) + var wg sync.WaitGroup + wg.Add(1) + + go func() { + // Our insurance policy to ensure that this test doesn't hang + // forever and should quickly report if/when we regress. + select { + case <-testDone: + t.Log("Test ended early enough") + case <-time.After(longDuration): + metricsClientDoneFn() + t.Errorf("Test took too long (%s) and is likely still hanging so this is a regression", longDuration) + } + wg.Done() + }() + + // Now the response should return an error and should have been torn down + // regardless of the number of times after invocation below, or any attempt + // to send the proper/corrective data should be rejected. + for i := 0; i < 10; i++ { + recv, err := metricsClient.Recv() + if recv != nil { + t.Errorf("Iteration #%d: Unexpectedly got back a response: %#v", i, recv) + } + if err == nil { + t.Errorf("Iteration #%d: Unexpectedly got back a nil error", i) + continue + } + + wantSubStr := "protocol violation: Export's first message must have a Node" + if g := err.Error(); !strings.Contains(g, wantSubStr) { + t.Errorf("Iteration #%d: Got error:\n\t%s\nWant substring:\n\t%s\n", i, g, wantSubStr) + } + + // The connection should be invalid at this point and + // no attempt to send corrections should succeed. + n1 := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 9489, HostName: "nodejs-host"}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_NODE_JS}, + } + if err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: n1}); err == nil { + t.Errorf("Iteration #%d: Unexpectedly succeeded in sending a message upstream. Connection must be in terminal state", i) + } else if g, w := err, io.EOF; g != w { + t.Errorf("Iteration #%d:\nGot error %q\nWant error %q", i, g, w) + } + } + + close(testDone) + wg.Wait() +} + +// If the first message is valid (has a non-nil Node) and has metrics, those +// metrics should be received and NEVER discarded. +// See https://github.com/census-instrumentation/opencensus-service/issues/51 +func TestExportProtocolConformation_metricsInFirstMessage(t *testing.T) { + // This test used to be flaky on Windows. Skip if errors pop up again + + metricSink := new(consumertest.MetricsSink) + + port, doneFn := ocReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + metricsClient, metricsClientDoneFn, err := makeMetricsServiceClient(port) + require.NoError(t, err, "Failed to create the gRPC MetricsService_ExportClient: %v", err) + defer metricsClientDoneFn() + + mLi := []*metricspb.Metric{makeMetric(10), makeMetric(11)} + ni := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 1}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_JAVA}, + } + err = metricsClient.Send(&agentmetricspb.ExportMetricsServiceRequest{Node: ni, Metrics: mLi}) + require.NoError(t, err, "Failed to send the first message: %v", err) + + // Give it time to be sent over the wire, then exported. + <-time.After(100 * time.Millisecond) + + // Examination time! + resultsMapping := make(map[string][]*metricspb.Metric) + for _, md := range metricSink.AllMetrics() { + ocmds := internaldata.MetricsToOC(md) + for _, ocmd := range ocmds { + resultsMapping[nodeToKey(ocmd.Node)] = append(resultsMapping[nodeToKey(ocmd.Node)], ocmd.Metrics...) + } + } + + if g, w := len(resultsMapping), 1; g != w { + t.Errorf("Results mapping: Got len(keys) %d Want %d", g, w) + } + + // Check for the keys + wantLengths := map[string]int{ + nodeToKey(ni): 2, + } + for key, wantLength := range wantLengths { + gotLength := len(resultsMapping[key]) + if gotLength != wantLength { + t.Errorf("Exported metrics:: Key: %s\nGot length %d\nWant length %d", key, gotLength, wantLength) + } + } + + // And finally ensure that the protos' serializations are equivalent to the expected + wantContents := map[string][]*metricspb.Metric{ + nodeToKey(ni): mLi, + } + + gotBlob, _ := json.Marshal(resultsMapping) + wantBlob, _ := json.Marshal(wantContents) + if !bytes.Equal(gotBlob, wantBlob) { + t.Errorf("Unequal serialization results\nGot:\n\t%s\nWant:\n\t%s\n", gotBlob, wantBlob) + } +} + +// Helper functions from here on below +func makeMetricsServiceClient(port int) (agentmetricspb.MetricsService_ExportClient, func(), error) { + addr := fmt.Sprintf(":%d", port) + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + return nil, nil, err + } + + svc := agentmetricspb.NewMetricsServiceClient(cc) + metricsClient, err := svc.Export(context.Background()) + if err != nil { + _ = cc.Close() + return nil, nil, err + } + + doneFn := func() { _ = cc.Close() } + return metricsClient, doneFn, nil +} + +func nodeToKey(n *commonpb.Node) string { + blob, _ := proto.Marshal(n) + return string(blob) +} + +func ocReceiverOnGRPCServer(t *testing.T, sr consumer.MetricsConsumer) (int, func()) { + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + doneFnList := []func(){func() { ln.Close() }} + done := func() { + for _, doneFn := range doneFnList { + doneFn() + } + } + + _, port, err := testutil.HostPortFromAddr(ln.Addr()) + if err != nil { + done() + t.Fatalf("Failed to parse host:port from listener address: %s error: %v", ln.Addr(), err) + } + + oci, err := New(receiverTagValue, sr) + require.NoError(t, err, "Failed to create the Receiver: %v", err) + + // Now run it as a gRPC server + srv := obsreport.GRPCServerWithObservabilityEnabled() + agentmetricspb.RegisterMetricsServiceServer(srv, oci) + go func() { + _ = srv.Serve(ln) + }() + + return port, done +} + +func makeMetric(val int) *metricspb.Metric { + key := &metricspb.LabelKey{ + Key: fmt.Sprintf("%s%d", "key", val), + } + value := &metricspb.LabelValue{ + Value: fmt.Sprintf("%s%d", "value", val), + HasValue: true, + } + + descriptor := &metricspb.MetricDescriptor{ + Name: fmt.Sprintf("%s%d", "metric_descriptort_", val), + Description: "metric descriptor", + Unit: "1", + Type: metricspb.MetricDescriptor_GAUGE_INT64, + LabelKeys: []*metricspb.LabelKey{key}, + } + + now := time.Now().UTC() + point := &metricspb.Point{ + Timestamp: timestamppb.New(now.Add(20 * time.Second)), + Value: &metricspb.Point_Int64Value{ + Int64Value: int64(val), + }, + } + + ts := &metricspb.TimeSeries{ + StartTimestamp: timestamppb.New(now.Add(-10 * time.Second)), + LabelValues: []*metricspb.LabelValue{value}, + Points: []*metricspb.Point{point}, + } + + return &metricspb.Metric{ + MetricDescriptor: descriptor, + Timeseries: []*metricspb.TimeSeries{ts}, + } +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/octrace/doc.go b/internal/otel_collector/receiver/opencensusreceiver/octrace/doc.go new file mode 100644 index 00000000000..4bf176ae24e --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/octrace/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package octrace is the logic for receiving OpenCensus trace protobuf defined spans from +// already instrumented applications and then passing them onto a TraceReceiverSink instance. +package octrace diff --git a/internal/otel_collector/receiver/opencensusreceiver/octrace/observability_test.go b/internal/otel_collector/receiver/opencensusreceiver/octrace/observability_test.go new file mode 100644 index 00000000000..15071f89315 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/octrace/observability_test.go @@ -0,0 +1,189 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package octrace + +import ( + "bytes" + "encoding/json" + "reflect" + "sync" + "testing" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/stretchr/testify/require" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +// Ensure that if we add a metrics exporter that our target metrics +// will be recorded but also with the proper tag keys and values. +// See Issue https://github.com/census-instrumentation/opencensus-service/issues/63 +// +// Note: we are intentionally skipping the ocgrpc.ServerDefaultViews as this +// test is to ensure exactness, but with the mentioned views registered, the +// output will be quite noisy. +func TestEnsureRecordedMetrics(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + port, doneReceiverFn := ocReceiverOnGRPCServer(t, consumertest.NewTracesNop()) + defer doneReceiverFn() + + n := 20 + // Now for the traceExporter that sends 0 length spans + traceSvcClient, traceSvcDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the trace service client: %v", err) + spans := []*tracepb.Span{{TraceId: []byte("abcdefghijklmnop"), SpanId: []byte("12345678")}} + for i := 0; i < n; i++ { + err = traceSvcClient.Send(&agenttracepb.ExportTraceServiceRequest{Spans: spans, Node: &commonpb.Node{}}) + require.NoError(t, err, "Failed to send requests to the service: %v", err) + } + flush(traceSvcDoneFn) + + obsreporttest.CheckReceiverTracesViews(t, "oc_trace", "grpc", int64(n), 0) +} + +func TestEnsureRecordedMetrics_zeroLengthSpansSender(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + port, doneFn := ocReceiverOnGRPCServer(t, consumertest.NewTracesNop()) + defer doneFn() + + n := 20 + // Now for the traceExporter that sends 0 length spans + traceSvcClient, traceSvcDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the trace service client: %v", err) + for i := 0; i <= n; i++ { + err = traceSvcClient.Send(&agenttracepb.ExportTraceServiceRequest{Spans: nil, Node: &commonpb.Node{}}) + require.NoError(t, err, "Failed to send requests to the service: %v", err) + } + flush(traceSvcDoneFn) + + obsreporttest.CheckReceiverTracesViews(t, "oc_trace", "grpc", 0, 0) +} + +func TestExportSpanLinkingMaintainsParentLink(t *testing.T) { + // Always sample for the purpose of examining all the spans in this test. + trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) + + // TODO: File an issue with OpenCensus-Go to ask for a method to retrieve + // the default sampler because the current method of blindly changing the + // global sampler makes testing hard. + // Denoise this test by setting the sampler to never sample + defer trace.ApplyConfig(trace.Config{DefaultSampler: trace.NeverSample()}) + + ocSpansSaver := new(testOCTraceExporter) + trace.RegisterExporter(ocSpansSaver) + defer trace.UnregisterExporter(ocSpansSaver) + + port, doneFn := ocReceiverOnGRPCServer(t, consumertest.NewTracesNop()) + defer doneFn() + + traceSvcClient, traceSvcDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the trace service client: %v", err) + + n := 5 + for i := 0; i < n; i++ { + sl := []*tracepb.Span{{TraceId: []byte("abcdefghijklmnop"), SpanId: []byte{byte(i + 1), 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}} + err = traceSvcClient.Send(&agenttracepb.ExportTraceServiceRequest{Spans: sl, Node: &commonpb.Node{}}) + require.NoError(t, err, "Failed to send requests to the service: %v", err) + } + + flush(traceSvcDoneFn) + + // Inspection time! + ocSpansSaver.mu.Lock() + defer ocSpansSaver.mu.Unlock() + + require.NotEqual( + t, + len(ocSpansSaver.spanData), + 0, + "Unfortunately did not receive an exported span data. Please check this library's implementation or go.opencensus.io/trace", + ) + + gotSpanData := ocSpansSaver.spanData + if g, w := len(gotSpanData), n+1; g != w { + blob, _ := json.MarshalIndent(gotSpanData, " ", " ") + t.Fatalf("Spandata count: Got %d Want %d\n\nData: %s", g, w, blob) + } + + receiverSpanData := gotSpanData[0] + if g, w := len(receiverSpanData.Links), 1; g != w { + t.Fatalf("Links count: Got %d Want %d\nGotSpanData: %#v", g, w, receiverSpanData) + } + + // The rpc span is always last in the list + rpcSpanData := gotSpanData[len(gotSpanData)-1] + + // Ensure that the link matches up exactly! + wantLink := trace.Link{ + SpanID: rpcSpanData.SpanID, + TraceID: rpcSpanData.TraceID, + Type: trace.LinkTypeParent, + } + if g, w := receiverSpanData.Links[0], wantLink; !reflect.DeepEqual(g, w) { + t.Errorf("Link:\nGot: %#v\nWant: %#v\n", g, w) + } + if g, w := receiverSpanData.Name, "receiver/oc_trace/TraceDataReceived"; g != w { + t.Errorf("ReceiverExport span's SpanData.Name:\nGot: %q\nWant: %q\n", g, w) + } + + // And then for the receiverSpanData itself, it SHOULD NOT + // have a ParentID, so let's enforce all the conditions below: + // 1. That it doesn't have the RPC spanID as its ParentSpanID + // 2. That it actually has no ParentSpanID i.e. has a blank SpanID + if g, w := receiverSpanData.ParentSpanID[:], rpcSpanData.SpanID[:]; bytes.Equal(g, w) { + t.Errorf("ReceiverSpanData.ParentSpanID unfortunately was linked to the RPC span\nGot: %x\nWant: %x", g, w) + } + + var blankSpanID trace.SpanID + if g, w := receiverSpanData.ParentSpanID[:], blankSpanID[:]; !bytes.Equal(g, w) { + t.Errorf("ReceiverSpanData unfortunately has a parent and isn't NULL\nGot: %x\nWant: %x", g, w) + } +} + +type testOCTraceExporter struct { + mu sync.Mutex + spanData []*trace.SpanData +} + +func (tote *testOCTraceExporter) ExportSpan(sd *trace.SpanData) { + tote.mu.Lock() + defer tote.mu.Unlock() + + tote.spanData = append(tote.spanData, sd) +} + +// TODO: Determine how to do this deterministic. +func flush(traceSvcDoneFn func()) { + // Give it enough time to process the streamed spans. + <-time.After(40 * time.Millisecond) + + // End the gRPC service to complete the RPC trace so that we + // can examine the RPC trace as well. + traceSvcDoneFn() + + // Give it some more time to complete the RPC trace and export. + <-time.After(40 * time.Millisecond) +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus.go b/internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus.go new file mode 100644 index 00000000000..cf490841535 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus.go @@ -0,0 +1,166 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package octrace + +import ( + "context" + "errors" + "io" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/translator/internaldata" +) + +const ( + receiverTagValue = "oc_trace" + receiverTransport = "grpc" // TODO: transport is being hard coded for now, investigate if info is available on context. + receiverDataFormat = "protobuf" +) + +// Receiver is the type used to handle spans from OpenCensus exporters. +type Receiver struct { + agenttracepb.UnimplementedTraceServiceServer + nextConsumer consumer.TracesConsumer + instanceName string +} + +// New creates a new opencensus.Receiver reference. +func New(instanceName string, nextConsumer consumer.TracesConsumer, opts ...Option) (*Receiver, error) { + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + ocr := &Receiver{ + nextConsumer: nextConsumer, + instanceName: instanceName, + } + for _, opt := range opts { + opt(ocr) + } + + return ocr, nil +} + +var _ agenttracepb.TraceServiceServer = (*Receiver)(nil) + +var errUnimplemented = errors.New("unimplemented") + +// Config handles configuration messages. +func (ocr *Receiver) Config(agenttracepb.TraceService_ConfigServer) error { + // TODO: Implement when we define the config receiver/sender. + return errUnimplemented +} + +var errTraceExportProtocolViolation = errors.New("protocol violation: Export's first message must have a Node") + +// Export is the gRPC method that receives streamed traces from +// OpenCensus-traceproto compatible libraries/applications. +func (ocr *Receiver) Export(tes agenttracepb.TraceService_ExportServer) error { + ctx := tes.Context() + if c, ok := client.FromGRPC(ctx); ok { + ctx = client.NewContext(ctx, c) + } + + longLivedRPCCtx := obsreport.ReceiverContext(ctx, ocr.instanceName, receiverTransport) + + // The first message MUST have a non-nil Node. + recv, err := tes.Recv() + if err != nil { + return err + } + + // Check the condition that the first message has a non-nil Node. + if recv.Node == nil { + return errTraceExportProtocolViolation + } + + var lastNonNilNode *commonpb.Node + var resource *resourcepb.Resource + // Now that we've got the first message with a Node, we can start to receive streamed up spans. + for { + lastNonNilNode, resource, err = ocr.processReceivedMsg( + longLivedRPCCtx, + lastNonNilNode, + resource, + recv) + if err != nil { + return err + } + + recv, err = tes.Recv() + if err != nil { + if err == io.EOF { + // Do not return EOF as an error so that grpc-gateway calls get an empty + // response with HTTP status code 200 rather than a 500 error with EOF. + return nil + } + return err + } + } +} + +func (ocr *Receiver) processReceivedMsg( + longLivedRPCCtx context.Context, + lastNonNilNode *commonpb.Node, + resource *resourcepb.Resource, + recv *agenttracepb.ExportTraceServiceRequest, +) (*commonpb.Node, *resourcepb.Resource, error) { + // If a Node has been sent from downstream, save and use it. + if recv.Node != nil { + lastNonNilNode = recv.Node + } + + // TODO(songya): differentiate between unset and nil resource. See + // https://github.com/census-instrumentation/opencensus-proto/issues/146. + if recv.Resource != nil { + resource = recv.Resource + } + + td := consumerdata.TraceData{ + Node: lastNonNilNode, + Resource: resource, + Spans: recv.Spans, + SourceFormat: "oc_trace", + } + + err := ocr.sendToNextConsumer(longLivedRPCCtx, td) + return lastNonNilNode, resource, err +} + +func (ocr *Receiver) sendToNextConsumer(longLivedRPCCtx context.Context, tracedata consumerdata.TraceData) error { + ctx := obsreport.StartTraceDataReceiveOp( + longLivedRPCCtx, + ocr.instanceName, + receiverTransport, + obsreport.WithLongLivedCtx()) + + var err error + numSpans := len(tracedata.Spans) + if numSpans != 0 { + err = ocr.nextConsumer.ConsumeTraces(ctx, internaldata.OCToTraceData(tracedata)) + } + + obsreport.EndTraceDataReceiveOp(ctx, receiverDataFormat, numSpans, err) + + return err +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus_test.go b/internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus_test.go new file mode 100644 index 00000000000..5d7bfd20f4a --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/octrace/opencensus_test.go @@ -0,0 +1,395 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package octrace + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "strings" + "testing" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/exporter/opencensusexporter" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/internaldata" +) + +func TestReceiver_endToEnd(t *testing.T) { + spanSink := new(consumertest.TracesSink) + + port, doneFn := ocReceiverOnGRPCServer(t, spanSink) + defer doneFn() + + address := fmt.Sprintf("localhost:%d", port) + expFactory := opencensusexporter.NewFactory() + expCfg := expFactory.CreateDefaultConfig().(*opencensusexporter.Config) + expCfg.GRPCClientSettings.TLSSetting.Insecure = true + expCfg.Endpoint = address + expCfg.WaitForReady = true + oce, err := expFactory.CreateTracesExporter(context.Background(), component.ExporterCreateParams{Logger: zap.NewNop()}, expCfg) + require.NoError(t, err) + err = oce.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err) + + defer func() { + require.NoError(t, oce.Shutdown(context.Background())) + }() + + td := testdata.GenerateTraceDataOneSpan() + assert.NoError(t, oce.ConsumeTraces(context.Background(), td)) + + testutil.WaitFor(t, func() bool { + return len(spanSink.AllTraces()) != 0 + }) + gotTraces := spanSink.AllTraces() + require.Len(t, gotTraces, 1) + assert.Equal(t, td, gotTraces[0]) +} + +// Issue #43. Export should support node multiplexing. +// The goal is to ensure that Receiver can always support +// a passthrough mode where it initiates Export normally by firstly +// receiving the initiator node. However ti should still be able to +// accept nodes from downstream sources, but if a node isn't specified in +// an exportTrace request, assume it is from the last received and non-nil node. +func TestExportMultiplexing(t *testing.T) { + spanSink := new(consumertest.TracesSink) + + port, doneFn := ocReceiverOnGRPCServer(t, spanSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the gRPC TraceService_ExportClient: %v", err) + defer traceClientDoneFn() + + // Step 1) The initiation. + initiatingNode := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{ + Pid: 1, + HostName: "multiplexer", + }, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_JAVA}, + } + + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: initiatingNode}) + require.NoError(t, err, "Failed to send the initiating message: %v", err) + + // Step 1a) Send some spans without a node, they should be registered as coming from the initiating node. + sLi := []*tracepb.Span{{TraceId: []byte("1234567890abcdef"), Status: &tracepb.Status{}}} + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: nil, Spans: sLi}) + require.NoError(t, err, "Failed to send the proxied message from app1: %v", err) + + // Step 2) Send a "proxied" trace message from app1 with "node1" + node1 := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 9489, HostName: "nodejs-host"}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_NODE_JS}, + } + sL1 := []*tracepb.Span{{TraceId: []byte("abcdefghijklmnop"), Name: &tracepb.TruncatableString{Value: "test"}, Status: &tracepb.Status{}}} + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: node1, Spans: sL1}) + require.NoError(t, err, "Failed to send the proxied message from app1: %v", err) + + // Step 3) Send a trace message without a node but with spans: this + // should be registered as belonging to the last used node i.e. "node1". + sLn1 := []*tracepb.Span{{TraceId: []byte("ABCDEFGHIJKLMNOP"), Status: &tracepb.Status{}}, {TraceId: []byte("1234567890abcdef"), Status: &tracepb.Status{}}} + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: nil, Spans: sLn1}) + require.NoError(t, err, "Failed to send the proxied message without a node: %v", err) + + // Step 4) Send a trace message from a differently proxied node "node2" from app2 + node2 := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 7752, HostName: "golang-host"}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_GO_LANG}, + } + sL2 := []*tracepb.Span{{TraceId: []byte("_B_D_F_H_J_L_N_O"), Status: &tracepb.Status{}}} + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: node2, Spans: sL2}) + require.NoError(t, err, "Failed to send the proxied message from app2: %v", err) + + // Step 5a) Send a trace message without a node but with spans: this + // should be registered as belonging to the last used node i.e. "node2". + sLn2a := []*tracepb.Span{{TraceId: []byte("_BCDEFGHIJKLMNO_"), Status: &tracepb.Status{}}, {TraceId: []byte("_234567890abcde_"), Status: &tracepb.Status{}}} + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: nil, Spans: sLn2a}) + require.NoError(t, err, "Failed to send the proxied message without a node: %v", err) + + // Step 5b) + sLn2b := []*tracepb.Span{{TraceId: []byte("_xxxxxxxxxxxxxx_"), Status: &tracepb.Status{}}, {TraceId: []byte("B234567890abcdAB"), Status: &tracepb.Status{}}} + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: nil, Spans: sLn2b}) + require.NoError(t, err, "Failed to send the proxied message without a node: %v", err) + // Give the process sometime to send data over the wire and perform batching + <-time.After(150 * time.Millisecond) + + // Examination time! + resultsMapping := make(map[string][]*tracepb.Span) + for _, td := range spanSink.AllTraces() { + octds := internaldata.TraceDataToOC(td) + for _, octd := range octds { + resultsMapping[nodeToKey(octd.Node)] = append(resultsMapping[nodeToKey(octd.Node)], octd.Spans...) + } + } + + // First things first, we expect exactly 3 unique keys + // 1. Initiating Node + // 2. Node 1 + // 3. Node 2 + if g, w := len(resultsMapping), 3; g != w { + t.Errorf("Got %d keys in the results map; Wanted exactly %d\n\nResultsMapping: %+v\n", g, w, resultsMapping) + } + + // Want span counts + wantSpanCounts := map[string]int{ + nodeToKey(initiatingNode): 1, + nodeToKey(node1): 3, + nodeToKey(node2): 5, + } + for key, wantSpanCounts := range wantSpanCounts { + gotSpanCounts := len(resultsMapping[key]) + if gotSpanCounts != wantSpanCounts { + t.Errorf("Key=%q gotSpanCounts %d wantSpanCounts %d", key, gotSpanCounts, wantSpanCounts) + } + } + + // Now ensure that the exported spans match up exactly with + // the nodes and the last seen node expectation/behavior. + // (or at least their serialized equivalents match up) + wantContents := map[string][]*tracepb.Span{ + nodeToKey(initiatingNode): sLi, + nodeToKey(node1): append(sL1, sLn1...), + nodeToKey(node2): append(sL2, append(sLn2a, sLn2b...)...), + } + + for nodeKey, wantSpans := range wantContents { + gotSpans, ok := resultsMapping[nodeKey] + if !ok { + t.Errorf("Wanted to find a node that was not found for key: %s", nodeKey) + } + if len(gotSpans) != len(wantSpans) { + t.Errorf("Unequal number of spans for nodeKey: %s", nodeKey) + } + for _, wantSpan := range wantSpans { + found := false + for _, gotSpan := range gotSpans { + wantStr, _ := json.Marshal(wantSpan) + gotStr, _ := json.Marshal(gotSpan) + if bytes.Equal(wantStr, gotStr) { + found = true + } + } + if !found { + t.Errorf("Unequal span serialization\nGot:\n\t%s\nWant:\n\t%s\n", gotSpans, wantSpans) + } + } + } +} + +// The first message without a Node MUST be rejected and teardown the connection. +// See https://github.com/census-instrumentation/opencensus-service/issues/53 +func TestExportProtocolViolations_nodelessFirstMessage(t *testing.T) { + spanSink := new(consumertest.TracesSink) + + port, doneFn := ocReceiverOnGRPCServer(t, spanSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the gRPC TraceService_ExportClient: %v", err) + defer traceClientDoneFn() + + // Send a Nodeless first message + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: nil}) + require.NoError(t, err, "Unexpectedly failed to send the first message: %v", err) + + longDuration := 2 * time.Second + testDone := make(chan struct{}) + goroutineDone := make(chan struct{}) + go func() { + // Our insurance policy to ensure that this test doesn't hang + // forever and should quickly report if/when we regress. + select { + case <-testDone: + t.Log("Test ended early enough") + case <-time.After(longDuration): + traceClientDoneFn() + t.Errorf("Test took too long (%s) and is likely still hanging so this is a regression", longDuration) + } + close(goroutineDone) + }() + + // Now the response should return an error and should have been torn down + // regardless of the number of times after invocation below, or any attempt + // to send the proper/corrective data should be rejected. + for i := 0; i < 10; i++ { + recv, err := traceClient.Recv() + if recv != nil { + t.Errorf("Iteration #%d: Unexpectedly got back a response: %#v", i, recv) + } + if err == nil { + t.Errorf("Iteration #%d: Unexpectedly got back a nil error", i) + continue + } + + wantSubStr := "protocol violation: Export's first message must have a Node" + if g := err.Error(); !strings.Contains(g, wantSubStr) { + t.Errorf("Iteration #%d: Got error:\n\t%s\nWant substring:\n\t%s\n", i, g, wantSubStr) + } + + // The connection should be invalid at this point and + // no attempt to send corrections should succeed. + n1 := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 9489, HostName: "nodejs-host"}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_NODE_JS}, + } + if err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: n1}); err == nil { + t.Errorf("Iteration #%d: Unexpectedly succeeded in sending a message upstream. Connection must be in terminal state", i) + } else if g, w := err, io.EOF; g != w { + t.Errorf("Iteration #%d:\nGot error %q\nWant error %q", i, g, w) + } + } + + close(testDone) + <-goroutineDone +} + +// If the first message is valid (has a non-nil Node) and has spans, those +// spans should be received and NEVER discarded. +// See https://github.com/census-instrumentation/opencensus-service/issues/51 +func TestExportProtocolConformation_spansInFirstMessage(t *testing.T) { + spanSink := new(consumertest.TracesSink) + + port, doneFn := ocReceiverOnGRPCServer(t, spanSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the gRPC TraceService_ExportClient: %v", err) + defer traceClientDoneFn() + + sLi := []*tracepb.Span{ + {TraceId: []byte("1234567890abcdef"), Status: &tracepb.Status{}}, + {TraceId: []byte("XXXXXXXXXXabcdef"), Status: &tracepb.Status{}}, + } + ni := &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{Pid: 1}, + LibraryInfo: &commonpb.LibraryInfo{Language: commonpb.LibraryInfo_JAVA}, + } + err = traceClient.Send(&agenttracepb.ExportTraceServiceRequest{Node: ni, Spans: sLi}) + require.NoError(t, err, "Failed to send the first message: %v", err) + + // Give it time to be sent over the wire, then exported. + <-time.After(100 * time.Millisecond) + + // Examination time! + resultsMapping := make(map[string][]*tracepb.Span) + for _, td := range spanSink.AllTraces() { + octds := internaldata.TraceDataToOC(td) + for _, octd := range octds { + resultsMapping[nodeToKey(octd.Node)] = append(resultsMapping[nodeToKey(octd.Node)], octd.Spans...) + } + } + + if g, w := len(resultsMapping), 1; g != w { + t.Errorf("Results mapping: Got len(keys) %d Want %d", g, w) + } + + // Check for the keys + wantLengths := map[string]int{ + nodeToKey(ni): 2, + } + for key, wantLength := range wantLengths { + gotLength := len(resultsMapping[key]) + if gotLength != wantLength { + t.Errorf("Exported spans:: Key: %s\nGot length %d\nWant length %d", key, gotLength, wantLength) + } + } + + // And finally ensure that the protos' serializations are equivalent to the expected + wantContents := map[string][]*tracepb.Span{ + nodeToKey(ni): sLi, + } + + gotBlob, _ := json.Marshal(resultsMapping) + wantBlob, _ := json.Marshal(wantContents) + if !bytes.Equal(gotBlob, wantBlob) { + t.Errorf("Unequal serialization results\nGot:\n\t%s\nWant:\n\t%s\n", gotBlob, wantBlob) + } +} + +// Helper functions from here on below +func makeTraceServiceClient(port int) (agenttracepb.TraceService_ExportClient, func(), error) { + addr := fmt.Sprintf(":%d", port) + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + return nil, nil, err + } + + svc := agenttracepb.NewTraceServiceClient(cc) + traceClient, err := svc.Export(context.Background()) + if err != nil { + _ = cc.Close() + return nil, nil, err + } + + doneFn := func() { _ = cc.Close() } + return traceClient, doneFn, nil +} + +func nodeToKey(n *commonpb.Node) string { + blob, _ := proto.Marshal(n) + return string(blob) +} + +func ocReceiverOnGRPCServer(t *testing.T, sr consumer.TracesConsumer) (int, func()) { + ln, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + doneFnList := []func(){func() { require.NoError(t, ln.Close()) }} + done := func() { + for _, doneFn := range doneFnList { + doneFn() + } + } + + _, port, err := testutil.HostPortFromAddr(ln.Addr()) + if err != nil { + done() + t.Fatalf("Failed to parse host:port from listener address: %s error: %v", ln.Addr(), err) + } + + oci, err := New(receiverTagValue, sr) + require.NoError(t, err, "Failed to create the Receiver: %v", err) + + // Now run it as a gRPC server + srv := obsreport.GRPCServerWithObservabilityEnabled() + agenttracepb.RegisterTraceServiceServer(srv, oci) + go func() { + _ = srv.Serve(ln) + }() + + return port, done +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/octrace/options.go b/internal/otel_collector/receiver/opencensusreceiver/octrace/options.go new file mode 100644 index 00000000000..a3477619ba9 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/octrace/options.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package octrace + +// Option interface defines for configuration settings to be applied to receivers. +// +// WithReceiver applies the configuration to the given receiver. +type Option func(*Receiver) diff --git a/internal/otel_collector/receiver/opencensusreceiver/opencensus.go b/internal/otel_collector/receiver/opencensusreceiver/opencensus.go new file mode 100644 index 00000000000..6535d91e5d7 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/opencensus.go @@ -0,0 +1,275 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusreceiver + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "sync" + + agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + gatewayruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/rs/cors" + "github.com/soheilhy/cmux" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/receiver/opencensusreceiver/ocmetrics" + "go.opentelemetry.io/collector/receiver/opencensusreceiver/octrace" +) + +// ocReceiver is the type that exposes Trace and Metrics reception. +type ocReceiver struct { + mu sync.Mutex + ln net.Listener + serverGRPC *grpc.Server + serverHTTP *http.Server + gatewayMux *gatewayruntime.ServeMux + corsOrigins []string + grpcServerOptions []grpc.ServerOption + + traceReceiverOpts []octrace.Option + + traceReceiver *octrace.Receiver + metricsReceiver *ocmetrics.Receiver + + traceConsumer consumer.TracesConsumer + metricsConsumer consumer.MetricsConsumer + + stopOnce sync.Once + startServerOnce sync.Once + startTraceReceiverOnce sync.Once + startMetricsReceiverOnce sync.Once + + instanceName string +} + +// newOpenCensusReceiver just creates the OpenCensus receiver services. It is the caller's +// responsibility to invoke the respective Start*Reception methods as well +// as the various Stop*Reception methods to end it. +func newOpenCensusReceiver( + instanceName string, + transport string, + addr string, + tc consumer.TracesConsumer, + mc consumer.MetricsConsumer, + opts ...ocOption, +) (*ocReceiver, error) { + // TODO: (@odeke-em) use options to enable address binding changes. + ln, err := net.Listen(transport, addr) + if err != nil { + return nil, fmt.Errorf("failed to bind to address %q: %v", addr, err) + } + + ocr := &ocReceiver{ + ln: ln, + corsOrigins: []string{}, // Disable CORS by default. + gatewayMux: gatewayruntime.NewServeMux(), + } + + for _, opt := range opts { + opt.withReceiver(ocr) + } + + ocr.instanceName = instanceName + ocr.traceConsumer = tc + ocr.metricsConsumer = mc + + return ocr, nil +} + +// Start runs the trace receiver on the gRPC server. Currently +// it also enables the metrics receiver too. +func (ocr *ocReceiver) Start(_ context.Context, host component.Host) error { + return ocr.start(host) +} + +func (ocr *ocReceiver) registerTraceConsumer() error { + var err = componenterror.ErrAlreadyStarted + + ocr.startTraceReceiverOnce.Do(func() { + ocr.traceReceiver, err = octrace.New( + ocr.instanceName, ocr.traceConsumer, ocr.traceReceiverOpts...) + if err == nil { + srv := ocr.grpcServer() + agenttracepb.RegisterTraceServiceServer(srv, ocr.traceReceiver) + } + }) + + return err +} + +func (ocr *ocReceiver) registerMetricsConsumer() error { + var err = componenterror.ErrAlreadyStarted + + ocr.startMetricsReceiverOnce.Do(func() { + ocr.metricsReceiver, err = ocmetrics.New( + ocr.instanceName, ocr.metricsConsumer) + if err == nil { + srv := ocr.grpcServer() + agentmetricspb.RegisterMetricsServiceServer(srv, ocr.metricsReceiver) + } + }) + return err +} + +func (ocr *ocReceiver) grpcServer() *grpc.Server { + ocr.mu.Lock() + defer ocr.mu.Unlock() + + if ocr.serverGRPC == nil { + ocr.serverGRPC = obsreport.GRPCServerWithObservabilityEnabled(ocr.grpcServerOptions...) + } + + return ocr.serverGRPC +} + +// Shutdown is a method to turn off receiving. +func (ocr *ocReceiver) Shutdown(context.Context) error { + if err := ocr.stop(); err != componenterror.ErrAlreadyStopped { + return err + } + return nil +} + +// start runs all the receivers/services namely, Trace and Metrics services. +func (ocr *ocReceiver) start(host component.Host) error { + hasConsumer := false + if ocr.traceConsumer != nil { + hasConsumer = true + if err := ocr.registerTraceConsumer(); err != nil && err != componenterror.ErrAlreadyStarted { + return err + } + } + + if ocr.metricsConsumer != nil { + hasConsumer = true + if err := ocr.registerMetricsConsumer(); err != nil && err != componenterror.ErrAlreadyStarted { + return err + } + } + + if !hasConsumer { + return errors.New("cannot start receiver: no consumers were specified") + } + + if err := ocr.startServer(host); err != nil && err != componenterror.ErrAlreadyStarted { + return err + } + + // At this point we've successfully started all the services/receivers. + // Add other start routines here. + return nil +} + +// stop stops the underlying gRPC server and all the services running on it. +func (ocr *ocReceiver) stop() error { + ocr.mu.Lock() + defer ocr.mu.Unlock() + + err := componenterror.ErrAlreadyStopped + ocr.stopOnce.Do(func() { + err = nil + + if ocr.serverHTTP != nil { + _ = ocr.serverHTTP.Close() + } + + if ocr.ln != nil { + _ = ocr.ln.Close() + } + + // TODO: @(odeke-em) investigate what utility invoking (*grpc.Server).Stop() + // gives us yet we invoke (net.Listener).Close(). + // Sure (*grpc.Server).Stop() enables proper shutdown but imposes + // a painful and artificial wait time that goes into 20+seconds yet most of our + // tests and code should be reactive in less than even 1second. + // ocr.serverGRPC.Stop() + }) + return err +} + +func (ocr *ocReceiver) httpServer() *http.Server { + ocr.mu.Lock() + defer ocr.mu.Unlock() + + if ocr.serverHTTP == nil { + var mux http.Handler = ocr.gatewayMux + if len(ocr.corsOrigins) > 0 { + co := cors.Options{AllowedOrigins: ocr.corsOrigins} + mux = cors.New(co).Handler(mux) + } + ocr.serverHTTP = &http.Server{Handler: mux} + } + + return ocr.serverHTTP +} + +func (ocr *ocReceiver) startServer(host component.Host) error { + err := componenterror.ErrAlreadyStarted + ocr.startServerOnce.Do(func() { + err = nil + // Register the grpc-gateway on the HTTP server mux + c := context.Background() + opts := []grpc.DialOption{grpc.WithInsecure()} + endpoint := ocr.ln.Addr().String() + + _, ok := ocr.ln.(*net.UnixListener) + if ok { + endpoint = "unix:" + endpoint + } + + err = agenttracepb.RegisterTraceServiceHandlerFromEndpoint(c, ocr.gatewayMux, endpoint, opts) + if err != nil { + return + } + + err = agentmetricspb.RegisterMetricsServiceHandlerFromEndpoint(c, ocr.gatewayMux, endpoint, opts) + if err != nil { + return + } + + // Start the gRPC and HTTP/JSON (grpc-gateway) servers on the same port. + m := cmux.New(ocr.ln) + grpcL := m.MatchWithWriters( + cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"), + cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc+proto")) + + httpL := m.Match(cmux.Any()) + go func() { + if errGrpc := ocr.serverGRPC.Serve(grpcL); errGrpc != nil { + host.ReportFatalError(errGrpc) + } + }() + go func() { + if errHTTP := ocr.httpServer().Serve(httpL); errHTTP != nil { + host.ReportFatalError(errHTTP) + } + }() + go func() { + if errServe := m.Serve(); errServe != nil { + host.ReportFatalError(errServe) + } + }() + }) + return err +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/opencensus_test.go b/internal/otel_collector/receiver/opencensusreceiver/opencensus_test.go new file mode 100644 index 00000000000..d23eae44312 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/opencensus_test.go @@ -0,0 +1,620 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//lint:file-ignore U1000 t.Skip() flaky test causes unused function warning. + +package opencensusreceiver + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "strings" + "testing" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/obsreport/obsreporttest" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/internaldata" +) + +const ocReceiverName = "oc_receiver_test" + +func TestGrpcGateway_endToEnd(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + + // Set the buffer count to 1 to make it flush the test span immediately. + sink := new(consumertest.TracesSink) + ocr, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, sink, nil) + require.NoError(t, err, "Failed to create trace receiver: %v", err) + + err = ocr.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to start trace receiver: %v", err) + defer ocr.Shutdown(context.Background()) + + // TODO(songy23): make starting server deterministic + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + url := fmt.Sprintf("http://%s/v1/trace", addr) + + // Verify that CORS is not enabled by default, but that it gives an 405 + // method not allowed error. + verifyCorsResp(t, url, "origin.com", 405, false) + + traceJSON := []byte(` + { + "node":{"identifier":{"hostName":"testHost"}}, + "spans":[ + { + "traceId":"W47/95gDgQPSabYzgT/GDA==", + "spanId":"7uGbfsPBsXM=", + "name":{"value":"testSpan"}, + "startTime":"2018-12-13T14:51:00Z", + "endTime":"2018-12-13T14:51:01Z", + "attributes": { + "attributeMap": { + "attr1": {"intValue": "55"} + } + } + } + ] + }`) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(traceJSON)) + require.NoError(t, err, "Error creating trace POST request: %v", err) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err, "Error posting trace to grpc-gateway server: %v", err) + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading response from trace grpc-gateway, %v", err) + } + respStr := string(respBytes) + + err = resp.Body.Close() + if err != nil { + t.Errorf("Error closing response body, %v", err) + } + + if resp.StatusCode != 200 { + t.Errorf("Unexpected status from trace grpc-gateway: %v", resp.StatusCode) + } + + if respStr != "" { + t.Errorf("Got unexpected response from trace grpc-gateway: %v", respStr) + } + + got := sink.AllTraces() + require.Len(t, got, 1) + gotOc := internaldata.TraceDataToOC(got[0]) + require.Len(t, gotOc, 1) + + want := consumerdata.TraceData{ + Node: &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{HostName: "testHost"}, + }, + Resource: &resourcepb.Resource{}, + Spans: []*tracepb.Span{ + { + TraceId: []byte{0x5B, 0x8E, 0xFF, 0xF7, 0x98, 0x3, 0x81, 0x3, 0xD2, 0x69, 0xB6, 0x33, 0x81, 0x3F, 0xC6, 0xC}, + SpanId: []byte{0xEE, 0xE1, 0x9B, 0x7E, 0xC3, 0xC1, 0xB1, 0x73}, + Name: &tracepb.TruncatableString{Value: "testSpan"}, + StartTime: timestamppb.New(time.Unix(1544712660, 0).UTC()), + EndTime: timestamppb.New(time.Unix(1544712661, 0).UTC()), + Attributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + "attr1": { + Value: &tracepb.AttributeValue_IntValue{IntValue: 55}, + }, + }, + }, + Status: &tracepb.Status{}, + }, + }, + SourceFormat: "oc_trace", + } + assert.True(t, proto.Equal(want.Node, gotOc[0].Node)) + assert.True(t, proto.Equal(want.Resource, gotOc[0].Resource)) + require.Len(t, want.Spans, 1) + require.Len(t, gotOc[0].Spans, 1) + assert.EqualValues(t, want.Spans[0], gotOc[0].Spans[0]) +} + +func TestTraceGrpcGatewayCors_endToEnd(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + corsOrigins := []string{"allowed-*.com"} + + ocr, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, consumertest.NewTracesNop(), nil, withCorsOrigins(corsOrigins)) + require.NoError(t, err, "Failed to create trace receiver: %v", err) + defer ocr.Shutdown(context.Background()) + + err = ocr.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to start trace receiver: %v", err) + + // TODO(songy23): make starting server deterministic + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + url := fmt.Sprintf("http://%s/v1/trace", addr) + + // Verify allowed domain gets responses that allow CORS. + verifyCorsResp(t, url, "allowed-origin.com", 200, true) + + // Verify disallowed domain gets responses that disallow CORS. + verifyCorsResp(t, url, "disallowed-origin.com", 200, false) +} + +func TestMetricsGrpcGatewayCors_endToEnd(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + corsOrigins := []string{"allowed-*.com"} + + ocr, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, nil, consumertest.NewMetricsNop(), withCorsOrigins(corsOrigins)) + require.NoError(t, err, "Failed to create metrics receiver: %v", err) + defer ocr.Shutdown(context.Background()) + + err = ocr.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err, "Failed to start metrics receiver: %v", err) + + // TODO(songy23): make starting server deterministic + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + url := fmt.Sprintf("http://%s/v1/metrics", addr) + + // Verify allowed domain gets responses that allow CORS. + verifyCorsResp(t, url, "allowed-origin.com", 200, true) + + // Verify disallowed domain gets responses that disallow CORS. + verifyCorsResp(t, url, "disallowed-origin.com", 200, false) +} + +func verifyCorsResp(t *testing.T, url string, origin string, wantStatus int, wantAllowed bool) { + req, err := http.NewRequest("OPTIONS", url, nil) + require.NoError(t, err, "Error creating trace OPTIONS request: %v", err) + req.Header.Set("Origin", origin) + req.Header.Set("Access-Control-Request-Method", "POST") + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err, "Error sending OPTIONS to grpc-gateway server: %v", err) + + err = resp.Body.Close() + if err != nil { + t.Errorf("Error closing OPTIONS response body, %v", err) + } + + if resp.StatusCode != wantStatus { + t.Errorf("Unexpected status from OPTIONS: %v", resp.StatusCode) + } + + gotAllowOrigin := resp.Header.Get("Access-Control-Allow-Origin") + gotAllowMethods := resp.Header.Get("Access-Control-Allow-Methods") + + wantAllowOrigin := "" + wantAllowMethods := "" + if wantAllowed { + wantAllowOrigin = origin + wantAllowMethods = "POST" + } + + if gotAllowOrigin != wantAllowOrigin { + t.Errorf("Unexpected Access-Control-Allow-Origin: %v", gotAllowOrigin) + } + if gotAllowMethods != wantAllowMethods { + t.Errorf("Unexpected Access-Control-Allow-Methods: %v", gotAllowMethods) + } +} + +func TestStopWithoutStartNeverCrashes(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + ocr, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, nil, nil) + require.NoError(t, err, "Failed to create an OpenCensus receiver: %v", err) + // Stop it before ever invoking Start*. + ocr.stop() +} + +func TestNewPortAlreadyUsed(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + ln, err := net.Listen("tcp", addr) + require.NoError(t, err, "failed to listen on %q: %v", addr, err) + defer ln.Close() + + r, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, nil, nil) + require.Error(t, err) + require.Nil(t, r) +} + +func TestMultipleStopReceptionShouldNotError(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + r, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, consumertest.NewTracesNop(), consumertest.NewMetricsNop()) + require.NoError(t, err) + require.NotNil(t, r) + + require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, r.Shutdown(context.Background())) +} + +func TestStartWithoutConsumersShouldFail(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + r, err := newOpenCensusReceiver(ocReceiverName, "tcp", addr, nil, nil) + require.NoError(t, err) + require.NotNil(t, r) + + require.Error(t, r.Start(context.Background(), componenttest.NewNopHost())) +} + +func tempSocketName(t *testing.T) string { + tmpfile, err := ioutil.TempFile("", "sock") + require.NoError(t, err) + require.NoError(t, tmpfile.Close()) + socket := tmpfile.Name() + require.NoError(t, os.Remove(socket)) + return socket +} + +func TestReceiveOnUnixDomainSocket_endToEnd(t *testing.T) { + socketName := tempSocketName(t) + cbts := consumertest.NewTracesNop() + r, err := newOpenCensusReceiver(ocReceiverName, "unix", socketName, cbts, nil) + require.NoError(t, err) + require.NotNil(t, r) + require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost())) + defer r.Shutdown(context.Background()) + + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + span := ` +{ + "node": { + }, + "spans": [ + { + "trace_id": "YpsR8/le4OgjwSSxhjlrEg==", + "span_id": "2CogcbJh7Ko=", + "socket": { + "value": "/abc", + "truncated_byte_count": 0 + }, + "kind": "SPAN_KIND_UNSPECIFIED", + "start_time": "2020-01-09T11:13:53.187Z", + "end_time": "2020-01-09T11:13:53.187Z" + } + ] +} +` + c := http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, err error) { + return net.Dial("unix", socketName) + }, + }, + } + + response, err := c.Post("http://unix/v1/trace", "application/json", strings.NewReader(span)) + require.NoError(t, err) + defer response.Body.Close() + + require.Equal(t, 200, response.StatusCode) +} + +// TestOCReceiverTrace_HandleNextConsumerResponse checks if the trace receiver +// is returning the proper response (return and metrics) when the next consumer +// in the pipeline reports error. The test changes the responses returned by the +// next trace consumer, checks if data was passed down the pipeline and if +// proper metrics were recorded. It also uses all endpoints supported by the +// trace receiver. +func TestOCReceiverTrace_HandleNextConsumerResponse(t *testing.T) { + type ingestionStateTest struct { + okToIngest bool + expectedCode codes.Code + } + tests := []struct { + name string + expectedReceivedBatches int + expectedIngestionBlockedRPCs int + ingestionStates []ingestionStateTest + }{ + { + name: "IngestTest", + expectedReceivedBatches: 2, + expectedIngestionBlockedRPCs: 1, + ingestionStates: []ingestionStateTest{ + { + okToIngest: true, + expectedCode: codes.OK, + }, + { + okToIngest: false, + expectedCode: codes.Unknown, + }, + { + okToIngest: true, + expectedCode: codes.OK, + }, + }, + }, + } + + addr := testutil.GetAvailableLocalAddress(t) + msg := &agenttracepb.ExportTraceServiceRequest{ + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: "test-svc"}, + }, + Spans: []*tracepb.Span{ + { + TraceId: []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + }, + }, + }, + } + + exportBidiFn := func( + t *testing.T, + cc *grpc.ClientConn, + msg *agenttracepb.ExportTraceServiceRequest) error { + + acc := agenttracepb.NewTraceServiceClient(cc) + stream, err := acc.Export(context.Background()) + require.NoError(t, err) + require.NotNil(t, stream) + + err = stream.Send(msg) + stream.CloseSend() + if err == nil { + for { + if _, err = stream.Recv(); err != nil { + if err == io.EOF { + err = nil + } + break + } + } + } + + return err + } + + exporters := []struct { + receiverTag string + exportFn func( + t *testing.T, + cc *grpc.ClientConn, + msg *agenttracepb.ExportTraceServiceRequest) error + }{ + { + receiverTag: "oc_trace", + exportFn: exportBidiFn, + }, + } + for _, exporter := range exporters { + for _, tt := range tests { + t.Run(tt.name+"/"+exporter.receiverTag, func(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + sink := new(consumertest.TracesSink) + + var opts []ocOption + ocr, err := newOpenCensusReceiver(exporter.receiverTag, "tcp", addr, nil, nil, opts...) + require.Nil(t, err) + require.NotNil(t, ocr) + + ocr.traceConsumer = sink + require.NoError(t, ocr.Start(context.Background(), componenttest.NewNopHost())) + defer ocr.Shutdown(context.Background()) + + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + t.Errorf("grpc.Dial: %v", err) + } + defer cc.Close() + + for _, ingestionState := range tt.ingestionStates { + if ingestionState.okToIngest { + sink.SetConsumeError(nil) + } else { + sink.SetConsumeError(fmt.Errorf("%q: consumer error", tt.name)) + } + + err = exporter.exportFn(t, cc, msg) + + status, ok := status.FromError(err) + require.True(t, ok) + assert.Equal(t, ingestionState.expectedCode, status.Code()) + } + + require.Equal(t, tt.expectedReceivedBatches, len(sink.AllTraces())) + obsreporttest.CheckReceiverTracesViews(t, exporter.receiverTag, "grpc", int64(tt.expectedReceivedBatches), int64(tt.expectedIngestionBlockedRPCs)) + }) + } + } +} + +// TestOCReceiverMetrics_HandleNextConsumerResponse checks if the metrics receiver +// is returning the proper response (return and metrics) when the next consumer +// in the pipeline reports error. The test changes the responses returned by the +// next trace consumer, checks if data was passed down the pipeline and if +// proper metrics were recorded. It also uses all endpoints supported by the +// metrics receiver. +func TestOCReceiverMetrics_HandleNextConsumerResponse(t *testing.T) { + type ingestionStateTest struct { + okToIngest bool + expectedCode codes.Code + } + tests := []struct { + name string + expectedReceivedBatches int + expectedIngestionBlockedRPCs int + ingestionStates []ingestionStateTest + }{ + { + name: "IngestTest", + expectedReceivedBatches: 2, + expectedIngestionBlockedRPCs: 1, + ingestionStates: []ingestionStateTest{ + { + okToIngest: true, + expectedCode: codes.OK, + }, + { + okToIngest: false, + expectedCode: codes.Unknown, + }, + { + okToIngest: true, + expectedCode: codes.OK, + }, + }, + }, + } + + descriptor := &metricspb.MetricDescriptor{ + Name: "testMetric", + Description: "metric descriptor", + Unit: "1", + Type: metricspb.MetricDescriptor_GAUGE_INT64, + } + point := &metricspb.Point{ + Timestamp: timestamppb.New(time.Now().UTC()), + Value: &metricspb.Point_Int64Value{ + Int64Value: int64(1), + }, + } + ts := &metricspb.TimeSeries{ + Points: []*metricspb.Point{point}, + } + metric := &metricspb.Metric{ + MetricDescriptor: descriptor, + Timeseries: []*metricspb.TimeSeries{ts}, + } + + addr := testutil.GetAvailableLocalAddress(t) + msg := &agentmetricspb.ExportMetricsServiceRequest{ + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: "test-svc"}, + }, + Metrics: []*metricspb.Metric{metric}, + } + + exportBidiFn := func( + t *testing.T, + cc *grpc.ClientConn, + msg *agentmetricspb.ExportMetricsServiceRequest) error { + + acc := agentmetricspb.NewMetricsServiceClient(cc) + stream, err := acc.Export(context.Background()) + require.NoError(t, err) + require.NotNil(t, stream) + + err = stream.Send(msg) + stream.CloseSend() + if err == nil { + for { + if _, err = stream.Recv(); err != nil { + if err == io.EOF { + err = nil + } + break + } + } + } + + return err + } + + exporters := []struct { + receiverTag string + exportFn func( + t *testing.T, + cc *grpc.ClientConn, + msg *agentmetricspb.ExportMetricsServiceRequest) error + }{ + { + receiverTag: "oc_metrics", + exportFn: exportBidiFn, + }, + } + for _, exporter := range exporters { + for _, tt := range tests { + t.Run(tt.name+"/"+exporter.receiverTag, func(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + sink := new(consumertest.MetricsSink) + + var opts []ocOption + ocr, err := newOpenCensusReceiver(exporter.receiverTag, "tcp", addr, nil, nil, opts...) + require.Nil(t, err) + require.NotNil(t, ocr) + + ocr.metricsConsumer = sink + require.Nil(t, ocr.Start(context.Background(), componenttest.NewNopHost())) + defer ocr.Shutdown(context.Background()) + + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + t.Errorf("grpc.Dial: %v", err) + } + defer cc.Close() + + for _, ingestionState := range tt.ingestionStates { + if ingestionState.okToIngest { + sink.SetConsumeError(nil) + } else { + sink.SetConsumeError(fmt.Errorf("%q: consumer error", tt.name)) + } + + err = exporter.exportFn(t, cc, msg) + + status, ok := status.FromError(err) + require.True(t, ok) + assert.Equal(t, ingestionState.expectedCode, status.Code()) + } + + require.Equal(t, tt.expectedReceivedBatches, len(sink.AllMetrics())) + obsreporttest.CheckReceiverMetricsViews(t, exporter.receiverTag, "grpc", int64(tt.expectedReceivedBatches), int64(tt.expectedIngestionBlockedRPCs)) + }) + } + } +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/options.go b/internal/otel_collector/receiver/opencensusreceiver/options.go new file mode 100644 index 00000000000..84fc48da142 --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/options.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opencensusreceiver + +import ( + "google.golang.org/grpc" +) + +// ocOption interface defines for configuration settings to be applied to receivers. +// +// withReceiver applies the configuration to the given receiver. +type ocOption interface { + withReceiver(*ocReceiver) +} + +type corsOrigins struct { + origins []string +} + +var _ ocOption = (*corsOrigins)(nil) + +func (co *corsOrigins) withReceiver(ocr *ocReceiver) { + ocr.corsOrigins = co.origins +} + +// withCorsOrigins is an option to specify the allowed origins to enable writing +// HTTP/JSON requests to the grpc-gateway adapter using CORS. +func withCorsOrigins(origins []string) ocOption { + return &corsOrigins{origins: origins} +} + +var _ ocOption = (grpcServerOptions)(nil) + +type grpcServerOptions []grpc.ServerOption + +func (gsvo grpcServerOptions) withReceiver(ocr *ocReceiver) { + ocr.grpcServerOptions = gsvo +} + +// withGRPCServerOptions allows one to specify the options for starting a gRPC server. +func withGRPCServerOptions(gsOpts ...grpc.ServerOption) ocOption { + gsvOpts := grpcServerOptions(gsOpts) + return gsvOpts +} diff --git a/internal/otel_collector/receiver/opencensusreceiver/testdata/config.yaml b/internal/otel_collector/receiver/opencensusreceiver/testdata/config.yaml new file mode 100644 index 00000000000..1de5dde787e --- /dev/null +++ b/internal/otel_collector/receiver/opencensusreceiver/testdata/config.yaml @@ -0,0 +1,69 @@ +receivers: + # The following entry initializes the default OpenCensus receiver. + # The default values are specified here + # https://github.com/open-telemetry/opentelemetry-collector/blob/71589202609d7e787244076b631b45e219101867/receiver/opencensusreceiver/factory.go#L47-L56 + # The full name of this receiver is `opencensus` and can be referenced in pipelines by 'opencensus'. + opencensus: + # The following entry demonstrates configuring the common receiver settings: + # - endpoint + # For more information on the struct, refer to + # https://github:com/open-telemetry/opentelemetry-service/blob/71589202609d7e787244076b631b45e219101867/config/configmodels/configmodels.go#L142-L150 + # This configuration is of type 'opencensus' and has the name 'customname' with a full name of 'opencensus/customname' + # ('/'. To reference this configuration in a pipeline, use the full name `opencensus/customname`. + opencensus/customname: + # The receiver will listen on endpoint: "0.0.0.0:9090". + endpoint: 0.0.0.0:9090 + # The following entry configures all of the keep alive settings. These settings are used to configure the receiver. + opencensus/keepalive: + keepalive: + server_parameters: + max_connection_idle: 11s + max_connection_age: 12s + max_connection_age_grace: 13s + time: 30s + timeout: 5s + enforcement_policy: + min_time: 10s + permit_without_stream: true + # The following demonstrates how to set maximum limits on stream, message size and connection idle time. + # Note: The test yaml has demonstrated configuration on a grouped by their structure; however, all of the settings can + # be mix and matched like adding the maximum connection idle setting in this example. + opencensus/msg-size-conc-connect-max-idle: + max_recv_msg_size_mib: 32 + max_concurrent_streams: 16 + read_buffer_size: 1024 + write_buffer_size: 1024 + keepalive: + server_parameters: + max_connection_idle: 10s + # The following entry demonstrates how to specify TLS credentials for the server. + # Note: These files do not exist. If the receiver is started with this configuration, it will fail. + opencensus/tlscredentials: + tls_settings: + cert_file: test.crt + key_file: test.key + # The following entry demonstrates how to specify a Unix Domain Socket for the server. + opencensus/uds: + transport: unix + endpoint: /tmp/opencensus.sock + # The following entry demonstrates how to configure the OpenCensus receiver to allow Cross-Origin Resource Sharing (CORS). + # Both fully qualified domain names and the use of wildcards are supported. + opencensus/cors: + cors_allowed_origins: + - https://*.test.com # Wildcard subdomain. Allows domains like https://www.test.com and https://foo.test.com but not https://wwwtest.com. + - https://test.com # Fully qualified domain name. Allows https://test.com only. +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [opencensus/customname] + processors: [exampleprocessor] + exporters: [exampleexporter] + metrics: + receivers: [opencensus] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/otlpreceiver/README.md b/internal/otel_collector/receiver/otlpreceiver/README.md new file mode 100644 index 00000000000..259590ac00c --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/README.md @@ -0,0 +1,67 @@ +# OTLP Receiver + +Receives data via gRPC or HTTP using [OTLP]( +https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/protocol/otlp.md) +format. + +Supported pipeline types: traces, metrics, logs + +:warning: OTLP metrics format is currently marked as "Alpha" and may change in +incompatible way any time. + +## Getting Started + +All that is required to enable the OTLP receiver is to include it in the +receiver definitions. A protocol can be disabled by simply not specifying it in +the list of protocols. + +```yaml +receivers: + otlp: + protocols: + grpc: + http: +``` + +The following settings are configurable: + +- `endpoint` (default = 0.0.0.0:4317 for grpc protocol, 0.0.0.0:55681 http protocol): + host:port to which the receiver is going to receive data. The valid syntax is + described at https://github.com/grpc/grpc/blob/master/doc/naming.md. + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) including CORS +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) + +## Writing with HTTP/JSON + +The OTLP receiver can receive trace export calls via HTTP/JSON in addition to +gRPC. The HTTP/JSON address is the same as gRPC as the protocol is recognized +and processed accordingly. Note the format needs to be [protobuf JSON +serialization](https://developers.google.com/protocol-buffers/docs/proto3#json). + +IMPORTANT: bytes fields are encoded as base64 strings. + +To write traces with HTTP/JSON, `POST` to `[address]/v1/traces` for traces, +to `[address]/v1/metrics` for metrics, to `[address]/v1/logs` for logs. The default +port is `55681`. + +The HTTP/JSON endpoint can also optionally configure +[CORS](https://fetch.spec.whatwg.org/#cors-protocol), which is enabled by +specifying a list of allowed CORS origins in the `cors_allowed_origins` field: + +```yaml +receivers: + otlp: + protocols: + http: + endpoint: "localhost:55681" + cors_allowed_origins: + - http://test.com + # Origins can have wildcards with *, use * by itself to match any origin. + - https://*.example.com +``` diff --git a/internal/otel_collector/receiver/otlpreceiver/config.go b/internal/otel_collector/receiver/otlpreceiver/config.go new file mode 100644 index 00000000000..85d4ca3f48c --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/config.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" +) + +type Protocols struct { + GRPC *configgrpc.GRPCServerSettings `mapstructure:"grpc"` + HTTP *confighttp.HTTPServerSettings `mapstructure:"http"` +} + +// Config defines configuration for OTLP receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON). + Protocols `mapstructure:"protocols"` +} diff --git a/internal/otel_collector/receiver/otlpreceiver/config_test.go b/internal/otel_collector/receiver/otlpreceiver/config_test.go new file mode 100644 index 00000000000..dd177adabb8 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/config_test.go @@ -0,0 +1,218 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/config/configtls" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 9) + + assert.Equal(t, cfg.Receivers["otlp"], factory.CreateDefaultConfig()) + + defaultOnlyGRPC := factory.CreateDefaultConfig().(*Config) + defaultOnlyGRPC.SetName("otlp/only_grpc") + defaultOnlyGRPC.HTTP = nil + assert.Equal(t, cfg.Receivers["otlp/only_grpc"], defaultOnlyGRPC) + + defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config) + defaultOnlyHTTP.SetName("otlp/only_http") + defaultOnlyHTTP.GRPC = nil + assert.Equal(t, cfg.Receivers["otlp/only_http"], defaultOnlyHTTP) + + assert.Equal(t, cfg.Receivers["otlp/customname"], + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "otlp/customname", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:9090", + Transport: "tcp", + }, + ReadBufferSize: 512 * 1024, + }, + }, + }) + + assert.Equal(t, cfg.Receivers["otlp/keepalive"], + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "otlp/keepalive", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:4317", + Transport: "tcp", + }, + ReadBufferSize: 512 * 1024, + Keepalive: &configgrpc.KeepaliveServerConfig{ + ServerParameters: &configgrpc.KeepaliveServerParameters{ + MaxConnectionIdle: 11 * time.Second, + MaxConnectionAge: 12 * time.Second, + MaxConnectionAgeGrace: 13 * time.Second, + Time: 30 * time.Second, + Timeout: 5 * time.Second, + }, + EnforcementPolicy: &configgrpc.KeepaliveEnforcementPolicy{ + MinTime: 10 * time.Second, + PermitWithoutStream: true, + }, + }, + }, + }, + }) + + assert.Equal(t, cfg.Receivers["otlp/msg-size-conc-connect-max-idle"], + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "otlp/msg-size-conc-connect-max-idle", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:4317", + Transport: "tcp", + }, + MaxRecvMsgSizeMiB: 32, + MaxConcurrentStreams: 16, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + Keepalive: &configgrpc.KeepaliveServerConfig{ + ServerParameters: &configgrpc.KeepaliveServerParameters{ + MaxConnectionIdle: 10 * time.Second, + }, + }, + }, + }, + }) + + // NOTE: Once the config loader checks for the files existence, this test may fail and require + // use of fake cert/key for test purposes. + assert.Equal(t, cfg.Receivers["otlp/tlscredentials"], + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "otlp/tlscredentials", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "0.0.0.0:4317", + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "test.crt", + KeyFile: "test.key", + }, + }, + ReadBufferSize: 512 * 1024, + }, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "0.0.0.0:55681", + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "test.crt", + KeyFile: "test.key", + }, + }, + }, + }, + }) + + assert.Equal(t, cfg.Receivers["otlp/cors"], + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "otlp/cors", + }, + Protocols: Protocols{ + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "0.0.0.0:55681", + CorsOrigins: []string{"https://*.test.com", "https://test.com"}, + }, + }, + }) + + assert.Equal(t, cfg.Receivers["otlp/uds"], + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "otlp/uds", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "/tmp/grpc_otlp.sock", + Transport: "unix", + }, + ReadBufferSize: 512 * 1024, + }, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "/tmp/http_otlp.sock", + // Transport: "unix", + }, + }, + }) +} + +func TestFailedLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "typo_default_proto_config.yaml"), factories) + assert.EqualError(t, err, `error reading receivers configuration for otlp: unknown protocols in the OTLP receiver`) + + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_proto_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for otlp: 1 error(s) decoding:\n\n* 'protocols' has invalid keys: thrift") + + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_no_proto_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for otlp: must specify at least one protocol when using the OTLP receiver") + + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "bad_empty_config.yaml"), factories) + assert.EqualError(t, err, "error reading receivers configuration for otlp: empty config for OTLP receiver") +} diff --git a/internal/otel_collector/receiver/otlpreceiver/factory.go b/internal/otel_collector/receiver/otlpreceiver/factory.go new file mode 100644 index 00000000000..75553059d16 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/factory.go @@ -0,0 +1,198 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "context" + "fmt" + + "github.com/spf13/viper" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "otlp" + + // Protocol values. + protoGRPC = "grpc" + protoHTTP = "http" + protocolsFieldName = "protocols" + + defaultGRPCEndpoint = "0.0.0.0:4317" + defaultHTTPEndpoint = "0.0.0.0:55681" + legacyGRPCEndpoint = "0.0.0.0:55680" +) + +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithTraces(createTraceReceiver), + receiverhelper.WithMetrics(createMetricsReceiver), + receiverhelper.WithLogs(createLogReceiver), + receiverhelper.WithCustomUnmarshaler(customUnmarshaler)) +} + +// createDefaultConfig creates the default configuration for receiver. +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCEndpoint, + Transport: "tcp", + }, + // We almost write 0 bytes, so no need to tune WriteBufferSize. + ReadBufferSize: 512 * 1024, + }, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + }, + } +} + +// customUnmarshaler is used to add defaults for named but empty protocols +func customUnmarshaler(componentViperSection *viper.Viper, intoCfg interface{}) error { + if componentViperSection == nil || len(componentViperSection.AllKeys()) == 0 { + return fmt.Errorf("empty config for OTLP receiver") + } + // first load the config normally + err := componentViperSection.UnmarshalExact(intoCfg) + if err != nil { + return err + } + + receiverCfg := intoCfg.(*Config) + // next manually search for protocols in viper, if a protocol is not present it means it is disable. + protocols := componentViperSection.GetStringMap(protocolsFieldName) + + // UnmarshalExact will ignore empty entries like a protocol with no values, so if a typo happened + // in the protocol that is intended to be enabled will not be enabled. So check if the protocols + // include only known protocols. + knownProtocols := 0 + if _, ok := protocols[protoGRPC]; !ok { + receiverCfg.GRPC = nil + } else { + knownProtocols++ + } + + if _, ok := protocols[protoHTTP]; !ok { + receiverCfg.HTTP = nil + } else { + knownProtocols++ + } + + if len(protocols) != knownProtocols { + return fmt.Errorf("unknown protocols in the OTLP receiver") + } + + if receiverCfg.GRPC == nil && receiverCfg.HTTP == nil { + return fmt.Errorf("must specify at least one protocol when using the OTLP receiver") + } + + return nil +} + +// CreateTracesReceiver creates a trace receiver based on provided config. +func createTraceReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer, +) (component.TracesReceiver, error) { + r, err := createReceiver(cfg, params.Logger) + if err != nil { + return nil, err + } + if err = r.registerTraceConsumer(ctx, nextConsumer); err != nil { + return nil, err + } + return r, nil +} + +// CreateMetricsReceiver creates a metrics receiver based on provided config. +func createMetricsReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + consumer consumer.MetricsConsumer, +) (component.MetricsReceiver, error) { + r, err := createReceiver(cfg, params.Logger) + if err != nil { + return nil, err + } + if err = r.registerMetricsConsumer(ctx, consumer); err != nil { + return nil, err + } + return r, nil +} + +// CreateLogReceiver creates a log receiver based on provided config. +func createLogReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + consumer consumer.LogsConsumer, +) (component.LogsReceiver, error) { + r, err := createReceiver(cfg, params.Logger) + if err != nil { + return nil, err + } + if err = r.registerLogsConsumer(ctx, consumer); err != nil { + return nil, err + } + return r, nil +} + +func createReceiver(cfg configmodels.Receiver, logger *zap.Logger) (*otlpReceiver, error) { + rCfg := cfg.(*Config) + + // There must be one receiver for both metrics and traces. We maintain a map of + // receivers per config. + + // Check to see if there is already a receiver for this config. + receiver, ok := receivers[rCfg] + if !ok { + var err error + // We don't have a receiver, so create one. + receiver, err = newOtlpReceiver(rCfg, logger) + if err != nil { + return nil, err + } + // Remember the receiver in the map + receivers[rCfg] = receiver + } + return receiver, nil +} + +// This is the map of already created OTLP receivers for particular configurations. +// We maintain this map because the Factory is asked trace and metric receivers separately +// when it gets CreateTracesReceiver() and CreateMetricsReceiver() but they must not +// create separate objects, they must use one otlpReceiver object per configuration. +var receivers = map[*Config]*otlpReceiver{} diff --git a/internal/otel_collector/receiver/otlpreceiver/factory_test.go b/internal/otel_collector/receiver/otlpreceiver/factory_test.go new file mode 100644 index 00000000000..60ea0b9094b --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/factory_test.go @@ -0,0 +1,354 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/testutil" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + config := cfg.(*Config) + config.GRPC.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t) + config.HTTP.Endpoint = testutil.GetAvailableLocalAddress(t) + + creationParams := component.ReceiverCreateParams{Logger: zap.NewNop()} + tReceiver, err := factory.CreateTracesReceiver(context.Background(), creationParams, cfg, new(consumertest.TracesSink)) + assert.NotNil(t, tReceiver) + assert.NoError(t, err) + + mReceiver, err := factory.CreateMetricsReceiver(context.Background(), creationParams, cfg, new(consumertest.MetricsSink)) + assert.NotNil(t, mReceiver) + assert.NoError(t, err) +} + +func TestCreateTraceReceiver(t *testing.T) { + factory := NewFactory() + defaultReceiverSettings := configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + defaultGRPCSettings := &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: testutil.GetAvailableLocalAddress(t), + Transport: "tcp", + }, + } + defaultHTTPSettings := &confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + tests := []struct { + name string + cfg *Config + wantErr bool + }{ + { + name: "default", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: defaultHTTPSettings, + }, + }, + }, + { + name: "invalid_grpc_port", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:112233", + Transport: "tcp", + }, + }, + HTTP: defaultHTTPSettings, + }, + }, + wantErr: true, + }, + { + name: "invalid_http_port", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "localhost:112233", + }, + }, + }, + wantErr: true, + }, + } + ctx := context.Background() + creationParams := component.ReceiverCreateParams{Logger: zap.NewNop()} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.TracesSink) + tr, err := factory.CreateTracesReceiver(ctx, creationParams, tt.cfg, sink) + assert.NoError(t, err) + require.NotNil(t, tr) + if tt.wantErr { + assert.Error(t, tr.Start(context.Background(), componenttest.NewNopHost())) + } else { + assert.NoError(t, tr.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, tr.Shutdown(context.Background())) + } + }) + } +} + +func TestCreateMetricReceiver(t *testing.T) { + factory := NewFactory() + defaultReceiverSettings := configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + defaultGRPCSettings := &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: testutil.GetAvailableLocalAddress(t), + Transport: "tcp", + }, + } + defaultHTTPSettings := &confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + tests := []struct { + name string + cfg *Config + wantErr bool + }{ + { + name: "default", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: defaultHTTPSettings, + }, + }, + }, + { + name: "invalid_grpc_address", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "327.0.0.1:1122", + Transport: "tcp", + }, + }, + HTTP: defaultHTTPSettings, + }, + }, + wantErr: true, + }, + { + name: "invalid_http_address", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "327.0.0.1:1122", + }, + }, + }, + wantErr: true, + }, + } + ctx := context.Background() + creationParams := component.ReceiverCreateParams{Logger: zap.NewNop()} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.MetricsSink) + mr, err := factory.CreateMetricsReceiver(ctx, creationParams, tt.cfg, sink) + assert.NoError(t, err) + require.NotNil(t, mr) + if tt.wantErr { + assert.Error(t, mr.Start(context.Background(), componenttest.NewNopHost())) + } else { + require.NoError(t, mr.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, mr.Shutdown(context.Background())) + } + }) + } +} + +func TestCreateLogReceiver(t *testing.T) { + factory := NewFactory() + defaultReceiverSettings := configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + } + defaultGRPCSettings := &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: testutil.GetAvailableLocalAddress(t), + Transport: "tcp", + }, + } + defaultHTTPSettings := &confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + } + + tests := []struct { + name string + cfg *Config + wantStartErr bool + wantErr bool + sink consumer.LogsConsumer + }{ + { + name: "default", + cfg: &Config{ + ReceiverSettings: defaultReceiverSettings, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: defaultHTTPSettings, + }, + }, + sink: new(consumertest.LogsSink), + }, + { + name: "invalid_grpc_address", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "327.0.0.1:1122", + Transport: "tcp", + }, + }, + HTTP: defaultHTTPSettings, + }, + }, + wantStartErr: true, + sink: new(consumertest.LogsSink), + }, + { + name: "invalid_http_address", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "327.0.0.1:1122", + }, + }, + }, + wantStartErr: true, + sink: new(consumertest.LogsSink), + }, + { + name: "no_next_consumer", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{ + GRPC: defaultGRPCSettings, + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: "327.0.0.1:1122", + }, + }, + }, + wantErr: true, + sink: nil, + }, + { + name: "no_http_or_grcp_config", + cfg: &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + Protocols: Protocols{}, + }, + wantErr: false, + sink: new(consumertest.LogsSink), + }, + } + ctx := context.Background() + creationParams := component.ReceiverCreateParams{Logger: zap.NewNop()} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mr, err := factory.CreateLogsReceiver(ctx, creationParams, tt.cfg, tt.sink) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + require.NotNil(t, mr) + + if tt.wantStartErr { + assert.Error(t, mr.Start(context.Background(), componenttest.NewNopHost())) + } else { + require.NoError(t, mr.Start(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, mr.Shutdown(context.Background())) + } + receivers = map[*Config]*otlpReceiver{} + }) + } +} diff --git a/internal/otel_collector/receiver/otlpreceiver/logs/otlp.go b/internal/otel_collector/receiver/otlpreceiver/logs/otlp.go new file mode 100644 index 00000000000..149a1c110bd --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/logs/otlp.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "context" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal" + collectorlog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + "go.opentelemetry.io/collector/obsreport" +) + +const ( + dataFormatProtobuf = "protobuf" +) + +// Receiver is the type used to handle spans from OpenTelemetry exporters. +type Receiver struct { + instanceName string + nextConsumer consumer.LogsConsumer +} + +// New creates a new Receiver reference. +func New(instanceName string, nextConsumer consumer.LogsConsumer) *Receiver { + r := &Receiver{ + instanceName: instanceName, + nextConsumer: nextConsumer, + } + + return r +} + +const ( + receiverTagValue = "otlp_log" + receiverTransport = "grpc" +) + +func (r *Receiver) Export(ctx context.Context, req *collectorlog.ExportLogsServiceRequest) (*collectorlog.ExportLogsServiceResponse, error) { + // We need to ensure that it propagates the receiver name as a tag + ctxWithReceiverName := obsreport.ReceiverContext(ctx, r.instanceName, receiverTransport) + + ld := pdata.LogsFromInternalRep(internal.LogsFromOtlp(req.ResourceLogs)) + err := r.sendToNextConsumer(ctxWithReceiverName, ld) + if err != nil { + return nil, err + } + + return &collectorlog.ExportLogsServiceResponse{}, nil +} + +func (r *Receiver) sendToNextConsumer(ctx context.Context, ld pdata.Logs) error { + numSpans := ld.LogRecordCount() + if numSpans == 0 { + return nil + } + + if c, ok := client.FromGRPC(ctx); ok { + ctx = client.NewContext(ctx, c) + } + + ctx = obsreport.StartLogsReceiveOp(ctx, r.instanceName, receiverTransport) + err := r.nextConsumer.ConsumeLogs(ctx, ld) + obsreport.EndLogsReceiveOp(ctx, dataFormatProtobuf, numSpans, err) + + return err +} diff --git a/internal/otel_collector/receiver/otlpreceiver/logs/otlp_test.go b/internal/otel_collector/receiver/otlpreceiver/logs/otlp_test.go new file mode 100644 index 00000000000..8faf2f3d7f1 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/logs/otlp_test.go @@ -0,0 +1,181 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "context" + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal" + "go.opentelemetry.io/collector/internal/data" + collectorlog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + otlplog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/logs/v1" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/testutil" +) + +var _ collectorlog.LogsServiceServer = (*Receiver)(nil) + +func TestExport(t *testing.T) { + // given + + logSink := new(consumertest.LogsSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, logSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeLogsServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer traceClientDoneFn() + + // when + + unixnanos := uint64(12578940000000012345) + traceID := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1} + spanID := [8]byte{8, 7, 6, 5, 4, 3, 2, 1} + resourceLogs := []*otlplog.ResourceLogs{ + { + InstrumentationLibraryLogs: []*otlplog.InstrumentationLibraryLogs{ + { + Logs: []*otlplog.LogRecord{ + { + TraceId: data.NewTraceID(traceID), + SpanId: data.NewSpanID(spanID), + Name: "operationB", + TimeUnixNano: unixnanos, + }, + }, + }, + }, + }, + } + + // Keep log data to compare the test result against it + // Clone needed because OTLP proto XXX_ fields are altered in the GRPC downstream + traceData := pdata.LogsFromInternalRep(internal.LogsFromOtlp(resourceLogs)).Clone() + + req := &collectorlog.ExportLogsServiceRequest{ + ResourceLogs: resourceLogs, + } + + resp, err := traceClient.Export(context.Background(), req) + require.NoError(t, err, "Failed to export trace: %v", err) + require.NotNil(t, resp, "The response is missing") + + // assert + + require.Equal(t, 1, len(logSink.AllLogs()), "unexpected length: %v", len(logSink.AllLogs())) + + assert.EqualValues(t, traceData, logSink.AllLogs()[0]) +} + +func TestExport_EmptyRequest(t *testing.T) { + logSink := new(consumertest.LogsSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, logSink) + defer doneFn() + + logClient, logClientDoneFn, err := makeLogsServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer logClientDoneFn() + + resp, err := logClient.Export(context.Background(), &collectorlog.ExportLogsServiceRequest{}) + assert.NoError(t, err, "Failed to export trace: %v", err) + assert.NotNil(t, resp, "The response is missing") +} + +func TestExport_ErrorConsumer(t *testing.T) { + logSink := new(consumertest.LogsSink) + logSink.SetConsumeError(fmt.Errorf("error")) + + port, doneFn := otlpReceiverOnGRPCServer(t, logSink) + defer doneFn() + + logClient, logClientDoneFn, err := makeLogsServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer logClientDoneFn() + + req := &collectorlog.ExportLogsServiceRequest{ + ResourceLogs: []*otlplog.ResourceLogs{ + { + InstrumentationLibraryLogs: []*otlplog.InstrumentationLibraryLogs{ + { + Logs: []*otlplog.LogRecord{ + { + Name: "operationB", + }, + }, + }, + }, + }, + }, + } + + resp, err := logClient.Export(context.Background(), req) + assert.EqualError(t, err, "rpc error: code = Unknown desc = error") + assert.Nil(t, resp) +} + +func makeLogsServiceClient(port int) (collectorlog.LogsServiceClient, func(), error) { + addr := fmt.Sprintf(":%d", port) + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + return nil, nil, err + } + + logClient := collectorlog.NewLogsServiceClient(cc) + + doneFn := func() { _ = cc.Close() } + return logClient, doneFn, nil +} + +func otlpReceiverOnGRPCServer(t *testing.T, tc consumer.LogsConsumer) (int, func()) { + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + doneFnList := []func(){func() { ln.Close() }} + done := func() { + for _, doneFn := range doneFnList { + doneFn() + } + } + + _, port, err := testutil.HostPortFromAddr(ln.Addr()) + if err != nil { + done() + t.Fatalf("Failed to parse host:port from listener address: %s error: %v", ln.Addr(), err) + } + + r := New(receiverTagValue, tc) + require.NoError(t, err) + + // Now run it as a gRPC server + srv := obsreport.GRPCServerWithObservabilityEnabled() + collectorlog.RegisterLogsServiceServer(srv, r) + go func() { + _ = srv.Serve(ln) + }() + + return port, done +} diff --git a/internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb.go b/internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb.go new file mode 100644 index 00000000000..22fb7814fc2 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb.go @@ -0,0 +1,301 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "reflect" + + "github.com/gogo/protobuf/jsonpb" + "github.com/gogo/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" +) + +// JSONPb is a copy of https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/marshal_jsonpb.go +// with one difference: github.com/golang/protobuf imports are replaced by github.com/gogo/protobuf +// to make it work with Gogoproto messages that we use. There are no other changes to +// JSONPb done. It should be safe to update (copy again) it to latest version of +// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/marshal_jsonpb.go +// when the github.com/grpc-ecosystem/grpc-gateway dependency is updated. + +//lint:file-ignore S1034 Ignore lint errors, this is a copied file and we don't want to modify it. + +// JSONPb is a Marshaler which marshals/unmarshals into/from JSON +// with the "github.com/golang/protobuf/jsonpb". +// It supports fully functionality of protobuf unlike JSONBuiltin. +// +// The NewDecoder method returns a DecoderWrapper, so the underlying +// *json.Decoder methods can be used. +type JSONPb jsonpb.Marshaler + +// ContentType always returns "application/json". +func (*JSONPb) ContentType() string { + return "application/json" +} + +// Marshal marshals "v" into JSON. +func (j *JSONPb) Marshal(v interface{}) ([]byte, error) { + if _, ok := v.(proto.Message); !ok { + return j.marshalNonProtoField(v) + } + + var buf bytes.Buffer + if err := j.marshalTo(&buf, v); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error { + p, ok := v.(proto.Message) + if !ok { + buf, err := j.marshalNonProtoField(v) + if err != nil { + return err + } + _, err = w.Write(buf) + return err + } + return (*jsonpb.Marshaler)(j).Marshal(w, p) +} + +var ( + // protoMessageType is stored to prevent constant lookup of the same type at runtime. + protoMessageType = reflect.TypeOf((*proto.Message)(nil)).Elem() +) + +// marshalNonProto marshals a non-message field of a protobuf message. +// This function does not correctly marshals arbitrary data structure into JSON, +// but it is only capable of marshaling non-message field values of protobuf, +// i.e. primitive types, enums; pointers to primitives or enums; maps from +// integer/string types to primitives/enums/pointers to messages. +func (j *JSONPb) marshalNonProtoField(v interface{}) ([]byte, error) { + if v == nil { + return []byte("null"), nil + } + rv := reflect.ValueOf(v) + for rv.Kind() == reflect.Ptr { + if rv.IsNil() { + return []byte("null"), nil + } + rv = rv.Elem() + } + + if rv.Kind() == reflect.Slice { + if rv.IsNil() { + if j.EmitDefaults { + return []byte("[]"), nil + } + return []byte("null"), nil + } + + if rv.Type().Elem().Implements(protoMessageType) { + var buf bytes.Buffer + err := buf.WriteByte('[') + if err != nil { + return nil, err + } + for i := 0; i < rv.Len(); i++ { + if i != 0 { + err = buf.WriteByte(',') + if err != nil { + return nil, err + } + } + if err = (*jsonpb.Marshaler)(j).Marshal(&buf, rv.Index(i).Interface().(proto.Message)); err != nil { + return nil, err + } + } + err = buf.WriteByte(']') + if err != nil { + return nil, err + } + + return buf.Bytes(), nil + } + } + + if rv.Kind() == reflect.Map { + m := make(map[string]*json.RawMessage) + for _, k := range rv.MapKeys() { + buf, err := j.Marshal(rv.MapIndex(k).Interface()) + if err != nil { + return nil, err + } + m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf) + } + if j.Indent != "" { + return json.MarshalIndent(m, "", j.Indent) + } + return json.Marshal(m) + } + if enum, ok := rv.Interface().(protoEnum); ok && !j.EnumsAsInts { + return json.Marshal(enum.String()) + } + return json.Marshal(rv.Interface()) +} + +// Unmarshal unmarshals JSON "data" into "v" +func (j *JSONPb) Unmarshal(data []byte, v interface{}) error { + return unmarshalJSONPb(data, v) +} + +// NewDecoder returns a Decoder which reads JSON stream from "r". +func (j *JSONPb) NewDecoder(r io.Reader) runtime.Decoder { + d := json.NewDecoder(r) + return DecoderWrapper{Decoder: d} +} + +// DecoderWrapper is a wrapper around a *json.Decoder that adds +// support for protos to the Decode method. +type DecoderWrapper struct { + *json.Decoder +} + +// Decode wraps the embedded decoder's Decode method to support +// protos using a jsonpb.Unmarshaler. +func (d DecoderWrapper) Decode(v interface{}) error { + return decodeJSONPb(d.Decoder, v) +} + +// NewEncoder returns an Encoder which writes JSON stream into "w". +func (j *JSONPb) NewEncoder(w io.Writer) runtime.Encoder { + return runtime.EncoderFunc(func(v interface{}) error { + if err := j.marshalTo(w, v); err != nil { + return err + } + // mimic json.Encoder by adding a newline (makes output + // easier to read when it contains multiple encoded items) + _, err := w.Write(j.Delimiter()) + return err + }) +} + +func unmarshalJSONPb(data []byte, v interface{}) error { + d := json.NewDecoder(bytes.NewReader(data)) + return decodeJSONPb(d, v) +} + +func decodeJSONPb(d *json.Decoder, v interface{}) error { + p, ok := v.(proto.Message) + if !ok { + return decodeNonProtoField(d, v) + } + unmarshaler := &jsonpb.Unmarshaler{AllowUnknownFields: allowUnknownFields} + return unmarshaler.UnmarshalNext(d, p) +} + +func decodeNonProtoField(d *json.Decoder, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + return fmt.Errorf("%T is not a pointer", v) + } + for rv.Kind() == reflect.Ptr { + if rv.IsNil() { + rv.Set(reflect.New(rv.Type().Elem())) + } + if rv.Type().ConvertibleTo(typeProtoMessage) { + unmarshaler := &jsonpb.Unmarshaler{AllowUnknownFields: allowUnknownFields} + return unmarshaler.UnmarshalNext(d, rv.Interface().(proto.Message)) + } + rv = rv.Elem() + } + if rv.Kind() == reflect.Map { + if rv.IsNil() { + rv.Set(reflect.MakeMap(rv.Type())) + } + conv, ok := convFromType[rv.Type().Key().Kind()] + if !ok { + return fmt.Errorf("unsupported type of map field key: %v", rv.Type().Key()) + } + + m := make(map[string]*json.RawMessage) + if err := d.Decode(&m); err != nil { + return err + } + for k, v := range m { + result := conv.Call([]reflect.Value{reflect.ValueOf(k)}) + if err := result[1].Interface(); err != nil { + return err.(error) + } + bk := result[0] + bv := reflect.New(rv.Type().Elem()) + if err := unmarshalJSONPb([]byte(*v), bv.Interface()); err != nil { + return err + } + rv.SetMapIndex(bk, bv.Elem()) + } + return nil + } + if _, ok := rv.Interface().(protoEnum); ok { + var repr interface{} + if err := d.Decode(&repr); err != nil { + return err + } + switch repr.(type) { + case string: + // TODO(yugui) Should use proto.StructProperties? + return fmt.Errorf("unmarshaling of symbolic enum %q not supported: %T", repr, rv.Interface()) + case float64: + rv.Set(reflect.ValueOf(int32(repr.(float64))).Convert(rv.Type())) + return nil + default: + return fmt.Errorf("cannot assign %#v into Go type %T", repr, rv.Interface()) + } + } + return d.Decode(v) +} + +type protoEnum interface { + fmt.Stringer + EnumDescriptor() ([]byte, []int) +} + +var typeProtoMessage = reflect.TypeOf((*proto.Message)(nil)).Elem() + +// Delimiter for newline encoded JSON streams. +func (j *JSONPb) Delimiter() []byte { + return []byte("\n") +} + +// allowUnknownFields helps not to return an error when the destination +// is a struct and the input contains object keys which do not match any +// non-ignored, exported fields in the destination. +var allowUnknownFields = true + +// DisallowUnknownFields enables option in decoder (unmarshaller) to +// return an error when it finds an unknown field. This function must be +// called before using the JSON marshaller. +func DisallowUnknownFields() { + allowUnknownFields = false +} + +// convFromType is an exact copy from https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/query.go +var ( + convFromType = map[reflect.Kind]reflect.Value{ + reflect.String: reflect.ValueOf(runtime.String), + reflect.Bool: reflect.ValueOf(runtime.Bool), + reflect.Float64: reflect.ValueOf(runtime.Float64), + reflect.Float32: reflect.ValueOf(runtime.Float32), + reflect.Int64: reflect.ValueOf(runtime.Int64), + reflect.Int32: reflect.ValueOf(runtime.Int32), + reflect.Uint64: reflect.ValueOf(runtime.Uint64), + reflect.Uint32: reflect.ValueOf(runtime.Uint32), + reflect.Slice: reflect.ValueOf(runtime.Bytes), + } +) diff --git a/internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb_test.go b/internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb_test.go new file mode 100644 index 00000000000..87e86b52160 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/marshal_jsonpb_test.go @@ -0,0 +1,103 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + v1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/testdata" +) + +const expectedJSON = `{ + "resource": { + "attributes": [ + { + "key": "resource-attr", + "value": { + "stringValue": "resource-attr-val-1" + } + } + ] + }, + "instrumentationLibrarySpans": [ + { + "instrumentationLibrary": {}, + "spans": [ + { + "traceId": "", + "spanId": "", + "parentSpanId": "", + "name": "operationA", + "startTimeUnixNano": "1581452772000000321", + "endTimeUnixNano": "1581452773000000789", + "droppedAttributesCount": 1, + "events": [ + { + "timeUnixNano": "1581452773000000123", + "name": "event-with-attr", + "attributes": [ + { + "key": "span-event-attr", + "value": { + "stringValue": "span-event-attr-val" + } + } + ], + "droppedAttributesCount": 2 + }, + { + "timeUnixNano": "1581452773000000123", + "name": "event", + "droppedAttributesCount": 2 + } + ], + "droppedEventsCount": 1, + "status": { + "deprecatedCode": "DEPRECATED_STATUS_CODE_UNKNOWN_ERROR", + "message": "status-cancelled", + "code": "STATUS_CODE_ERROR" + } + } + ] + } + ] +}` + +func TestJSONPbMarshal(t *testing.T) { + jpb := JSONPb{ + Indent: " ", + } + td := testdata.GenerateTraceDataOneSpan() + otlp := pdata.TracesToOtlp(td) + bytes, err := jpb.Marshal(otlp[0]) + assert.NoError(t, err) + assert.JSONEq(t, expectedJSON, string(bytes)) +} + +func TestJSONPbUnmarshal(t *testing.T) { + jpb := JSONPb{ + Indent: " ", + } + var proto v1.ResourceSpans + err := jpb.Unmarshal([]byte(expectedJSON), &proto) + assert.NoError(t, err) + td := testdata.GenerateTraceDataOneSpan() + otlp := pdata.TracesToOtlp(td) + assert.EqualValues(t, &proto, otlp[0]) +} diff --git a/internal/otel_collector/receiver/otlpreceiver/metrics/otlp.go b/internal/otel_collector/receiver/otlpreceiver/metrics/otlp.go new file mode 100644 index 00000000000..c5a16f3ebf9 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/metrics/otlp.go @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "context" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + collectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + "go.opentelemetry.io/collector/obsreport" +) + +const ( + dataFormatProtobuf = "protobuf" +) + +// Receiver is the type used to handle metrics from OpenTelemetry exporters. +type Receiver struct { + instanceName string + nextConsumer consumer.MetricsConsumer +} + +// New creates a new Receiver reference. +func New(instanceName string, nextConsumer consumer.MetricsConsumer) *Receiver { + r := &Receiver{ + instanceName: instanceName, + nextConsumer: nextConsumer, + } + return r +} + +const ( + receiverTagValue = "otlp_metrics" + receiverTransport = "grpc" +) + +func (r *Receiver) Export(ctx context.Context, req *collectormetrics.ExportMetricsServiceRequest) (*collectormetrics.ExportMetricsServiceResponse, error) { + receiverCtx := obsreport.ReceiverContext(ctx, r.instanceName, receiverTransport) + + md := pdata.MetricsFromOtlp(req.ResourceMetrics) + + err := r.sendToNextConsumer(receiverCtx, md) + if err != nil { + return nil, err + } + + return &collectormetrics.ExportMetricsServiceResponse{}, nil +} + +func (r *Receiver) sendToNextConsumer(ctx context.Context, md pdata.Metrics) error { + metricCount, dataPointCount := md.MetricAndDataPointCount() + if metricCount == 0 { + return nil + } + + if c, ok := client.FromGRPC(ctx); ok { + ctx = client.NewContext(ctx, c) + } + + ctx = obsreport.StartMetricsReceiveOp(ctx, r.instanceName, receiverTransport) + err := r.nextConsumer.ConsumeMetrics(ctx, md) + obsreport.EndMetricsReceiveOp(ctx, dataFormatProtobuf, dataPointCount, err) + + return err +} diff --git a/internal/otel_collector/receiver/otlpreceiver/metrics/otlp_test.go b/internal/otel_collector/receiver/otlpreceiver/metrics/otlp_test.go new file mode 100644 index 00000000000..4de14f2b472 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/metrics/otlp_test.go @@ -0,0 +1,222 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "context" + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + collectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/testutil" +) + +var _ collectormetrics.MetricsServiceServer = (*Receiver)(nil) + +func TestExport(t *testing.T) { + // given + + metricSink := new(consumertest.MetricsSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + metricsClient, metricsClientDoneFn, err := makeMetricsServiceClient(port) + require.NoError(t, err, "Failed to create the MetricsServiceClient: %v", err) + defer metricsClientDoneFn() + + // when + + unixnanos1 := uint64(12578940000000012345) + unixnanos2 := uint64(12578940000000054321) + + resourceMetrics := []*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + { + Name: "mymetric", + Description: "My metric", + Unit: "ms", + Data: &otlpmetrics.Metric_IntSum{ + IntSum: &otlpmetrics.IntSum{ + IsMonotonic: true, + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key1", + Value: "value1", + }, + }, + StartTimeUnixNano: unixnanos1, + TimeUnixNano: unixnanos2, + Value: 123, + }, + { + Labels: []otlpcommon.StringKeyValue{ + { + Key: "key2", + Value: "value2", + }, + }, + StartTimeUnixNano: unixnanos1, + TimeUnixNano: unixnanos2, + Value: 456, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + // Keep metric data to compare the test result against it + // Clone needed because OTLP proto XXX_ fields are altered in the GRPC downstream + metricData := pdata.MetricsFromOtlp(resourceMetrics).Clone() + + req := &collectormetrics.ExportMetricsServiceRequest{ + ResourceMetrics: resourceMetrics, + } + + resp, err := metricsClient.Export(context.Background(), req) + require.NoError(t, err, "Failed to export metrics: %v", err) + require.NotNil(t, resp, "The response is missing") + + // assert + + require.Equal(t, 1, len(metricSink.AllMetrics()), + "unexpected length: %v", len(metricSink.AllMetrics())) + + assert.EqualValues(t, metricData, metricSink.AllMetrics()[0]) +} + +func TestExport_EmptyRequest(t *testing.T) { + // given + + metricSink := new(consumertest.MetricsSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + metricsClient, metricsClientDoneFn, err := makeMetricsServiceClient(port) + require.NoError(t, err, "Failed to create the MetricsServiceClient: %v", err) + defer metricsClientDoneFn() + + resp, err := metricsClient.Export(context.Background(), &collectormetrics.ExportMetricsServiceRequest{}) + require.NoError(t, err) + require.NotNil(t, resp) +} + +func TestExport_ErrorConsumer(t *testing.T) { + // given + + metricSink := new(consumertest.MetricsSink) + metricSink.SetConsumeError(fmt.Errorf("error")) + + port, doneFn := otlpReceiverOnGRPCServer(t, metricSink) + defer doneFn() + + metricsClient, metricsClientDoneFn, err := makeMetricsServiceClient(port) + require.NoError(t, err, "Failed to create the MetricsServiceClient: %v", err) + defer metricsClientDoneFn() + + req := &collectormetrics.ExportMetricsServiceRequest{ResourceMetrics: []*otlpmetrics.ResourceMetrics{ + { + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + { + Name: "mymetric", + Description: "My metric", + Unit: "ms", + Data: &otlpmetrics.Metric_IntSum{ + IntSum: &otlpmetrics.IntSum{ + IsMonotonic: true, + AggregationTemporality: otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE, + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Value: 123, + }, + { + Value: 456, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }} + resp, err := metricsClient.Export(context.Background(), req) + assert.EqualError(t, err, "rpc error: code = Unknown desc = error") + assert.Nil(t, resp) +} + +func makeMetricsServiceClient(port int) (collectormetrics.MetricsServiceClient, func(), error) { + addr := fmt.Sprintf(":%d", port) + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + return nil, nil, err + } + + metricsClient := collectormetrics.NewMetricsServiceClient(cc) + + doneFn := func() { _ = cc.Close() } + return metricsClient, doneFn, nil +} + +func otlpReceiverOnGRPCServer(t *testing.T, mc consumer.MetricsConsumer) (int, func()) { + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + doneFnList := []func(){func() { ln.Close() }} + done := func() { + for _, doneFn := range doneFnList { + doneFn() + } + } + + _, port, err := testutil.HostPortFromAddr(ln.Addr()) + require.NoError(t, err) + + r := New(receiverTagValue, mc) + // Now run it as a gRPC server + srv := obsreport.GRPCServerWithObservabilityEnabled() + collectormetrics.RegisterMetricsServiceServer(srv, r) + go func() { + _ = srv.Serve(ln) + }() + + return port, done +} diff --git a/internal/otel_collector/receiver/otlpreceiver/mixin.go b/internal/otel_collector/receiver/otlpreceiver/mixin.go new file mode 100644 index 00000000000..06be24b7e24 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/mixin.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "context" + + gatewayruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + collectorlog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + collectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + "go.opentelemetry.io/collector/receiver/otlpreceiver/logs" + "go.opentelemetry.io/collector/receiver/otlpreceiver/metrics" + "go.opentelemetry.io/collector/receiver/otlpreceiver/trace" +) + +// RegisterTraceReceiver registers the trace receiver with a gRPC server and/or grpc-gateway mux, if non-nil. +func RegisterTraceReceiver(ctx context.Context, receiver *trace.Receiver, serverGRPC *grpc.Server, gatewayMux *gatewayruntime.ServeMux) error { + if serverGRPC != nil { + collectortrace.RegisterTraceServiceServer(serverGRPC, receiver) + } + if gatewayMux != nil { + err := collectortrace.RegisterTraceServiceHandlerServer(ctx, gatewayMux, receiver) + if err != nil { + return err + } + // Also register an alias handler. This fixes bug https://github.com/open-telemetry/opentelemetry-collector/issues/1968 + return collectortrace.RegisterTraceServiceHandlerServerAlias(ctx, gatewayMux, receiver) + } + return nil +} + +// RegisterMetricsReceiver registers the metrics receiver with a gRPC server and/or grpc-gateway mux, if non-nil. +func RegisterMetricsReceiver(ctx context.Context, receiver *metrics.Receiver, serverGRPC *grpc.Server, gatewayMux *gatewayruntime.ServeMux) error { + if serverGRPC != nil { + collectormetrics.RegisterMetricsServiceServer(serverGRPC, receiver) + } + if gatewayMux != nil { + return collectormetrics.RegisterMetricsServiceHandlerServer(ctx, gatewayMux, receiver) + } + return nil +} + +// RegisterLogsReceiver registers the logs receiver with a gRPC server and/or grpc-gateway mux, if non-nil. +func RegisterLogsReceiver(ctx context.Context, receiver *logs.Receiver, serverGRPC *grpc.Server, gatewayMux *gatewayruntime.ServeMux) error { + if serverGRPC != nil { + collectorlog.RegisterLogsServiceServer(serverGRPC, receiver) + } + if gatewayMux != nil { + return collectorlog.RegisterLogsServiceHandlerServer(ctx, gatewayMux, receiver) + } + return nil +} diff --git a/internal/otel_collector/receiver/otlpreceiver/otlp.go b/internal/otel_collector/receiver/otlpreceiver/otlp.go new file mode 100644 index 00000000000..6601e0ee84a --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/otlp.go @@ -0,0 +1,232 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "context" + "errors" + "net" + "net/http" + "sync" + + gatewayruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "go.uber.org/zap" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/consumer" + collectorlog "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/logs/v1" + collectormetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/metrics/v1" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + "go.opentelemetry.io/collector/receiver/otlpreceiver/logs" + "go.opentelemetry.io/collector/receiver/otlpreceiver/metrics" + "go.opentelemetry.io/collector/receiver/otlpreceiver/trace" +) + +// otlpReceiver is the type that exposes Trace and Metrics reception. +type otlpReceiver struct { + cfg *Config + serverGRPC *grpc.Server + gatewayMux *gatewayruntime.ServeMux + serverHTTP *http.Server + + traceReceiver *trace.Receiver + metricsReceiver *metrics.Receiver + logReceiver *logs.Receiver + + stopOnce sync.Once + startServerOnce sync.Once + + logger *zap.Logger +} + +// newOtlpReceiver just creates the OpenTelemetry receiver services. It is the caller's +// responsibility to invoke the respective Start*Reception methods as well +// as the various Stop*Reception methods to end it. +func newOtlpReceiver(cfg *Config, logger *zap.Logger) (*otlpReceiver, error) { + r := &otlpReceiver{ + cfg: cfg, + logger: logger, + } + if cfg.GRPC != nil { + opts, err := cfg.GRPC.ToServerOption() + if err != nil { + return nil, err + } + r.serverGRPC = grpc.NewServer(opts...) + } + if cfg.HTTP != nil { + // Use our custom JSON marshaler instead of default Protobuf JSON marshaler. + // This is needed because OTLP spec defines encoding for trace and span id + // and it is only possible to do using Gogoproto-compatible JSONPb marshaler. + jsonpb := &JSONPb{ + EmitDefaults: true, + Indent: " ", + OrigName: true, + } + r.gatewayMux = gatewayruntime.NewServeMux( + gatewayruntime.WithProtoErrorHandler(gatewayruntime.DefaultHTTPProtoErrorHandler), + gatewayruntime.WithMarshalerOption("application/x-protobuf", &xProtobufMarshaler{}), + gatewayruntime.WithMarshalerOption(gatewayruntime.MIMEWildcard, jsonpb), + ) + } + + return r, nil +} + +func (r *otlpReceiver) startGRPCServer(cfg *configgrpc.GRPCServerSettings, host component.Host) error { + r.logger.Info("Starting GRPC server on endpoint " + cfg.NetAddr.Endpoint) + var gln net.Listener + gln, err := cfg.ToListener() + if err != nil { + return err + } + go func() { + if errGrpc := r.serverGRPC.Serve(gln); errGrpc != nil { + host.ReportFatalError(errGrpc) + } + }() + return nil +} + +func (r *otlpReceiver) startHTTPServer(cfg *confighttp.HTTPServerSettings, host component.Host) error { + r.logger.Info("Starting HTTP server on endpoint " + cfg.Endpoint) + var hln net.Listener + hln, err := r.cfg.HTTP.ToListener() + if err != nil { + return err + } + go func() { + if errHTTP := r.serverHTTP.Serve(hln); errHTTP != nil { + host.ReportFatalError(errHTTP) + } + }() + return nil +} + +func (r *otlpReceiver) startProtocolServers(host component.Host) error { + var err error + if r.cfg.GRPC != nil { + err = r.startGRPCServer(r.cfg.GRPC, host) + if err != nil { + return err + } + if r.cfg.GRPC.NetAddr.Endpoint == defaultGRPCEndpoint { + r.logger.Info("Setting up a second GRPC listener on legacy endpoint " + legacyGRPCEndpoint) + + // Copy the config. + cfgLegacyGRPC := r.cfg.GRPC + // And use the legacy endpoint. + cfgLegacyGRPC.NetAddr.Endpoint = legacyGRPCEndpoint + err = r.startGRPCServer(cfgLegacyGRPC, host) + if err != nil { + return err + } + } + } + if r.cfg.HTTP != nil { + r.serverHTTP = r.cfg.HTTP.ToServer( + r.gatewayMux, + confighttp.WithErrorHandler(errorHandler), + ) + err = r.startHTTPServer(r.cfg.HTTP, host) + if err != nil { + return err + } + } + + return err +} + +// Start runs the trace receiver on the gRPC server. Currently +// it also enables the metrics receiver too. +func (r *otlpReceiver) Start(_ context.Context, host component.Host) error { + if r.traceReceiver == nil && r.metricsReceiver == nil && r.logReceiver == nil { + return errors.New("cannot start receiver: no consumers were specified") + } + + var err error + r.startServerOnce.Do(func() { + err = r.startProtocolServers(host) + }) + return err +} + +// Shutdown is a method to turn off receiving. +func (r *otlpReceiver) Shutdown(context.Context) error { + var err error + r.stopOnce.Do(func() { + err = nil + + if r.serverHTTP != nil { + err = r.serverHTTP.Close() + } + + if r.serverGRPC != nil { + r.serverGRPC.Stop() + } + }) + return err +} + +func (r *otlpReceiver) registerTraceConsumer(ctx context.Context, tc consumer.TracesConsumer) error { + if tc == nil { + return componenterror.ErrNilNextConsumer + } + r.traceReceiver = trace.New(r.cfg.Name(), tc) + if r.serverGRPC != nil { + collectortrace.RegisterTraceServiceServer(r.serverGRPC, r.traceReceiver) + } + if r.gatewayMux != nil { + err := collectortrace.RegisterTraceServiceHandlerServer(ctx, r.gatewayMux, r.traceReceiver) + if err != nil { + return err + } + // Also register an alias handler. This fixes bug https://github.com/open-telemetry/opentelemetry-collector/issues/1968 + return collectortrace.RegisterTraceServiceHandlerServerAlias(ctx, r.gatewayMux, r.traceReceiver) + } + return nil +} + +func (r *otlpReceiver) registerMetricsConsumer(ctx context.Context, mc consumer.MetricsConsumer) error { + if mc == nil { + return componenterror.ErrNilNextConsumer + } + r.metricsReceiver = metrics.New(r.cfg.Name(), mc) + if r.serverGRPC != nil { + collectormetrics.RegisterMetricsServiceServer(r.serverGRPC, r.metricsReceiver) + } + if r.gatewayMux != nil { + return collectormetrics.RegisterMetricsServiceHandlerServer(ctx, r.gatewayMux, r.metricsReceiver) + } + return nil +} + +func (r *otlpReceiver) registerLogsConsumer(ctx context.Context, tc consumer.LogsConsumer) error { + if tc == nil { + return componenterror.ErrNilNextConsumer + } + r.logReceiver = logs.New(r.cfg.Name(), tc) + if r.serverGRPC != nil { + collectorlog.RegisterLogsServiceServer(r.serverGRPC, r.logReceiver) + } + if r.gatewayMux != nil { + return collectorlog.RegisterLogsServiceHandlerServer(ctx, r.gatewayMux, r.logReceiver) + } + return nil +} diff --git a/internal/otel_collector/receiver/otlpreceiver/otlp_test.go b/internal/otel_collector/receiver/otlpreceiver/otlp_test.go new file mode 100644 index 00000000000..a14a1c09da9 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/otlp_test.go @@ -0,0 +1,777 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "testing" + "time" + + "github.com/gogo/protobuf/jsonpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + spb "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/data" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/obsreport/obsreporttest" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/conventions" +) + +const otlpReceiverName = "otlp_receiver_test" + +var traceJSON = []byte(` + { + "resource_spans": [ + { + "resource": { + "attributes": [ + { + "key": "host.name", + "value": { "stringValue": "testHost" } + } + ] + }, + "instrumentation_library_spans": [ + { + "spans": [ + { + "trace_id": "5B8EFFF798038103D269B633813FC60C", + "span_id": "EEE19B7EC3C1B173", + "name": "testSpan", + "start_time_unix_nano": 1544712660000000000, + "end_time_unix_nano": 1544712661000000000, + "attributes": [ + { + "key": "attr1", + "value": { "intValue": 55 } + } + ] + } + ] + } + ] + } + ] + }`) + +var resourceSpansOtlp = otlptrace.ResourceSpans{ + + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: conventions.AttributeHostName, + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "testHost"}}, + }, + }, + }, + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + TraceId: data.NewTraceID([16]byte{0x5B, 0x8E, 0xFF, 0xF7, 0x98, 0x3, 0x81, 0x3, 0xD2, 0x69, 0xB6, 0x33, 0x81, 0x3F, 0xC6, 0xC}), + SpanId: data.NewSpanID([8]byte{0xEE, 0xE1, 0x9B, 0x7E, 0xC3, 0xC1, 0xB1, 0x73}), + Name: "testSpan", + StartTimeUnixNano: 1544712660000000000, + EndTimeUnixNano: 1544712661000000000, + Attributes: []otlpcommon.KeyValue{ + { + Key: "attr1", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_IntValue{IntValue: 55}}, + }, + }, + }, + }, + }, + }, +} + +var traceOtlp = pdata.TracesFromOtlp([]*otlptrace.ResourceSpans{&resourceSpansOtlp}) + +func TestJsonHttp(t *testing.T) { + tests := []struct { + name string + encoding string + err error + }{ + { + name: "JSONUncompressed", + encoding: "", + }, + { + name: "JSONGzipCompressed", + encoding: "gzip", + }, + { + name: "NotGRPCError", + encoding: "", + err: errors.New("my error"), + }, + { + name: "GRPCError", + encoding: "", + err: status.New(codes.Internal, "").Err(), + }, + } + addr := testutil.GetAvailableLocalAddress(t) + + // Set the buffer count to 1 to make it flush the test span immediately. + sink := new(consumertest.TracesSink) + ocr := newHTTPReceiver(t, addr, sink, nil) + + require.NoError(t, ocr.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver") + defer ocr.Shutdown(context.Background()) + + // TODO(nilebox): make starting server deterministic + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + // Previously we used /v1/trace as the path. The correct path according to OTLP spec + // is /v1/traces. We currently support both on the receiving side to give graceful + // period for senders to roll out a fix, so we test for both paths to make sure + // the receiver works correctly. + targetURLPaths := []string{"/v1/trace", "/v1/traces"} + + for _, test := range tests { + for _, targetURLPath := range targetURLPaths { + t.Run(test.name+targetURLPath, func(t *testing.T) { + url := fmt.Sprintf("http://%s%s", addr, targetURLPath) + sink.Reset() + testHTTPJSONRequest(t, url, sink, test.encoding, test.err) + }) + } + } +} + +func testHTTPJSONRequest(t *testing.T, url string, sink *consumertest.TracesSink, encoding string, expectedErr error) { + var buf *bytes.Buffer + var err error + switch encoding { + case "gzip": + buf, err = compressGzip(traceJSON) + require.NoError(t, err, "Error while gzip compressing trace: %v", err) + default: + buf = bytes.NewBuffer(traceJSON) + } + sink.SetConsumeError(expectedErr) + req, err := http.NewRequest("POST", url, buf) + require.NoError(t, err, "Error creating trace POST request: %v", err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Encoding", encoding) + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err, "Error posting trace to grpc-gateway server: %v", err) + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading response from trace grpc-gateway, %v", err) + } + respStr := string(respBytes) + err = resp.Body.Close() + if err != nil { + t.Errorf("Error closing response body, %v", err) + } + + allTraces := sink.AllTraces() + if expectedErr == nil { + assert.Equal(t, 200, resp.StatusCode) + var respJSON map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(respStr), &respJSON)) + assert.Len(t, respJSON, 0, "Got unexpected response from trace grpc-gateway") + + require.Len(t, allTraces, 1) + + got := allTraces[0] + assert.EqualValues(t, got, traceOtlp) + } else { + errStatus := &spb.Status{} + assert.NoError(t, json.Unmarshal([]byte(respStr), errStatus)) + if s, ok := status.FromError(expectedErr); ok { + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + assert.True(t, proto.Equal(errStatus, s.Proto())) + } else { + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + assert.True(t, proto.Equal(errStatus, &spb.Status{Code: int32(codes.Unknown), Message: "my error"})) + } + require.Len(t, allTraces, 0) + } + +} + +func TestJsonMarshaling(t *testing.T) { + m := jsonpb.Marshaler{} + json, err := m.MarshalToString(&resourceSpansOtlp) + assert.NoError(t, err) + + var resourceSpansOtlp2 otlptrace.ResourceSpans + err = jsonpb.UnmarshalString(json, &resourceSpansOtlp2) + assert.NoError(t, err) + + assert.EqualValues(t, resourceSpansOtlp, resourceSpansOtlp2) +} + +func TestJsonUnmarshaling(t *testing.T) { + var resourceSpansOtlp2 otlptrace.ResourceSpans + err := jsonpb.UnmarshalString(` + { + "instrumentation_library_spans": [ + { + "spans": [ + { + } + ] + } + ] + }`, &resourceSpansOtlp2) + assert.NoError(t, err) + assert.EqualValues(t, data.TraceID{}, resourceSpansOtlp2.InstrumentationLibrarySpans[0].Spans[0].TraceId) + + tests := []struct { + name string + json string + bytes [16]byte + }{ + { + name: "empty string trace id", + json: `""`, + bytes: [16]byte{}, + }, + { + name: "zero bytes trace id", + json: `"00000000000000000000000000000000"`, + bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var resourceSpansOtlp2 otlptrace.ResourceSpans + jsonStr := fmt.Sprintf(` + { + "instrumentation_library_spans": [ + { + "spans": [ + { + "trace_id": %v + } + ] + } + ] + }`, test.json) + err := jsonpb.UnmarshalString(jsonStr, &resourceSpansOtlp2) + assert.NoError(t, err) + assert.EqualValues(t, data.NewTraceID(test.bytes), resourceSpansOtlp2.InstrumentationLibrarySpans[0].Spans[0].TraceId) + }) + } +} + +func TestProtoHttp(t *testing.T) { + tests := []struct { + name string + encoding string + err error + }{ + { + name: "ProtoUncompressed", + encoding: "", + }, + { + name: "ProtoGzipCompressed", + encoding: "gzip", + }, + { + name: "NotGRPCError", + encoding: "", + err: errors.New("my error"), + }, + { + name: "GRPCError", + encoding: "", + err: status.New(codes.Internal, "").Err(), + }, + } + addr := testutil.GetAvailableLocalAddress(t) + + // Set the buffer count to 1 to make it flush the test span immediately. + tSink := new(consumertest.TracesSink) + mSink := new(consumertest.MetricsSink) + ocr := newHTTPReceiver(t, addr, tSink, mSink) + + require.NoError(t, ocr.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver") + defer ocr.Shutdown(context.Background()) + + // TODO(nilebox): make starting server deterministic + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + wantOtlp := pdata.TracesToOtlp(testdata.GenerateTraceDataOneSpan()) + traceProto := collectortrace.ExportTraceServiceRequest{ + ResourceSpans: wantOtlp, + } + traceBytes, err := traceProto.Marshal() + if err != nil { + t.Errorf("Error marshaling protobuf: %v", err) + } + + // Previously we used /v1/trace as the path. The correct path according to OTLP spec + // is /v1/traces. We currently support both on the receiving side to give graceful + // period for senders to roll out a fix, so we test for both paths to make sure + // the receiver works correctly. + targetURLPaths := []string{"/v1/trace", "/v1/traces"} + + for _, test := range tests { + for _, targetURLPath := range targetURLPaths { + t.Run(test.name+targetURLPath, func(t *testing.T) { + url := fmt.Sprintf("http://%s%s", addr, targetURLPath) + tSink.Reset() + testHTTPProtobufRequest(t, url, tSink, test.encoding, traceBytes, test.err, wantOtlp) + }) + } + } +} +func testHTTPProtobufRequest( + t *testing.T, + url string, + tSink *consumertest.TracesSink, + encoding string, + traceBytes []byte, + expectedErr error, + wantOtlp []*otlptrace.ResourceSpans, +) { + var buf *bytes.Buffer + var err error + switch encoding { + case "gzip": + buf, err = compressGzip(traceBytes) + require.NoError(t, err, "Error while gzip compressing trace: %v", err) + default: + buf = bytes.NewBuffer(traceBytes) + } + tSink.SetConsumeError(expectedErr) + req, err := http.NewRequest("POST", url, buf) + require.NoError(t, err, "Error creating trace POST request: %v", err) + req.Header.Set("Content-Type", "application/x-protobuf") + req.Header.Set("Content-Encoding", encoding) + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err, "Error posting trace to grpc-gateway server: %v", err) + + respBytes, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err, "Error reading response from trace grpc-gateway") + require.NoError(t, resp.Body.Close(), "Error closing response body") + + allTraces := tSink.AllTraces() + + require.Equal(t, "application/x-protobuf", resp.Header.Get("Content-Type"), "Unexpected response Content-Type") + + if expectedErr == nil { + require.Equal(t, 200, resp.StatusCode, "Unexpected return status") + tmp := &collectortrace.ExportTraceServiceResponse{} + err = tmp.Unmarshal(respBytes) + require.NoError(t, err, "Unable to unmarshal response to ExportTraceServiceResponse proto") + + require.Len(t, allTraces, 1) + + gotOtlp := pdata.TracesToOtlp(allTraces[0]) + + if len(gotOtlp) != len(wantOtlp) { + t.Fatalf("len(traces):\nGot: %d\nWant: %d\n", len(gotOtlp), len(wantOtlp)) + } + + got := gotOtlp[0] + want := wantOtlp[0] + + if !assert.EqualValues(t, got, want) { + t.Errorf("Sending trace proto over http failed\nGot:\n%v\nWant:\n%v\n", + got.String(), + want.String()) + } + } else { + errStatus := &spb.Status{} + assert.NoError(t, proto.Unmarshal(respBytes, errStatus)) + if s, ok := status.FromError(expectedErr); ok { + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + assert.True(t, proto.Equal(errStatus, s.Proto())) + } else { + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + assert.True(t, proto.Equal(errStatus, &spb.Status{Code: int32(codes.Unknown), Message: "my error"})) + } + require.Len(t, allTraces, 0) + } +} + +func TestOTLPReceiverInvalidContentEncoding(t *testing.T) { + tests := []struct { + name string + content string + encoding string + reqBodyFunc func() (*bytes.Buffer, error) + resBodyFunc func() ([]byte, error) + status int + }{ + { + name: "JsonGzipUncompressed", + content: "application/json", + encoding: "gzip", + reqBodyFunc: func() (*bytes.Buffer, error) { + return bytes.NewBuffer([]byte(`{"key": "value"}`)), nil + }, + resBodyFunc: func() ([]byte, error) { + return json.Marshal(status.New(codes.InvalidArgument, "gzip: invalid header").Proto()) + }, + status: 400, + }, + { + name: "ProtoGzipUncompressed", + content: "application/x-protobuf", + encoding: "gzip", + reqBodyFunc: func() (*bytes.Buffer, error) { + return bytes.NewBuffer([]byte(`{"key": "value"}`)), nil + }, + resBodyFunc: func() ([]byte, error) { + return proto.Marshal(status.New(codes.InvalidArgument, "gzip: invalid header").Proto()) + }, + status: 400, + }, + } + addr := testutil.GetAvailableLocalAddress(t) + + // Set the buffer count to 1 to make it flush the test span immediately. + tSink := new(consumertest.TracesSink) + mSink := new(consumertest.MetricsSink) + ocr := newHTTPReceiver(t, addr, tSink, mSink) + + require.NoError(t, ocr.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver") + defer ocr.Shutdown(context.Background()) + + url := fmt.Sprintf("http://%s/v1/traces", addr) + + // Wait for the servers to start + <-time.After(10 * time.Millisecond) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + body, err := test.reqBodyFunc() + require.NoError(t, err, "Error creating request body: %v", err) + + req, err := http.NewRequest("POST", url, body) + require.NoError(t, err, "Error creating trace POST request: %v", err) + req.Header.Set("Content-Type", test.content) + req.Header.Set("Content-Encoding", test.encoding) + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err, "Error posting trace to grpc-gateway server: %v", err) + + respBytes, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err, "Error reading response from trace grpc-gateway") + exRespBytes, err := test.resBodyFunc() + require.NoError(t, err, "Error creating expecting response body") + require.NoError(t, resp.Body.Close(), "Error closing response body") + + require.Equal(t, test.status, resp.StatusCode, "Unexpected return status") + require.Equal(t, test.content, resp.Header.Get("Content-Type"), "Unexpected response Content-Type") + require.Equal(t, exRespBytes, respBytes, "Unexpected response content") + }) + } +} + +func TestGRPCNewPortAlreadyUsed(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + ln, err := net.Listen("tcp", addr) + require.NoError(t, err, "failed to listen on %q: %v", addr, err) + defer ln.Close() + + r := newGRPCReceiver(t, otlpReceiverName, addr, new(consumertest.TracesSink), new(consumertest.MetricsSink)) + require.NotNil(t, r) + + require.Error(t, r.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestHTTPNewPortAlreadyUsed(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + ln, err := net.Listen("tcp", addr) + require.NoError(t, err, "failed to listen on %q: %v", addr, err) + defer ln.Close() + + r := newHTTPReceiver(t, addr, new(consumertest.TracesSink), new(consumertest.MetricsSink)) + require.NotNil(t, r) + + require.Error(t, r.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestGRPCStartWithoutConsumers(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + r := newGRPCReceiver(t, otlpReceiverName, addr, nil, nil) + require.NotNil(t, r) + require.Error(t, r.Start(context.Background(), componenttest.NewNopHost())) +} + +func TestHTTPStartWithoutConsumers(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + r := newHTTPReceiver(t, addr, nil, nil) + require.NotNil(t, r) + require.Error(t, r.Start(context.Background(), componenttest.NewNopHost())) +} + +// TestOTLPReceiverTrace_HandleNextConsumerResponse checks if the trace receiver +// is returning the proper response (return and metrics) when the next consumer +// in the pipeline reports error. The test changes the responses returned by the +// next trace consumer, checks if data was passed down the pipeline and if +// proper metrics were recorded. It also uses all endpoints supported by the +// trace receiver. +func TestOTLPReceiverTrace_HandleNextConsumerResponse(t *testing.T) { + type ingestionStateTest struct { + okToIngest bool + expectedCode codes.Code + } + tests := []struct { + name string + expectedReceivedBatches int + expectedIngestionBlockedRPCs int + ingestionStates []ingestionStateTest + }{ + { + name: "IngestTest", + expectedReceivedBatches: 2, + expectedIngestionBlockedRPCs: 1, + ingestionStates: []ingestionStateTest{ + { + okToIngest: true, + expectedCode: codes.OK, + }, + { + okToIngest: false, + expectedCode: codes.Unknown, + }, + { + okToIngest: true, + expectedCode: codes.OK, + }, + }, + }, + } + + addr := testutil.GetAvailableLocalAddress(t) + req := &collectortrace.ExportTraceServiceRequest{ + ResourceSpans: []*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + TraceId: data.NewTraceID( + [16]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + }, + ), + }, + }, + }, + }, + }, + }, + } + + exportBidiFn := func( + t *testing.T, + cc *grpc.ClientConn, + msg *collectortrace.ExportTraceServiceRequest) error { + + acc := collectortrace.NewTraceServiceClient(cc) + _, err := acc.Export(context.Background(), req) + + return err + } + + exporters := []struct { + receiverTag string + exportFn func( + t *testing.T, + cc *grpc.ClientConn, + msg *collectortrace.ExportTraceServiceRequest) error + }{ + { + receiverTag: "otlp_trace", + exportFn: exportBidiFn, + }, + } + for _, exporter := range exporters { + for _, tt := range tests { + t.Run(tt.name+"/"+exporter.receiverTag, func(t *testing.T) { + doneFn, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer doneFn() + + sink := new(consumertest.TracesSink) + + ocr := newGRPCReceiver(t, exporter.receiverTag, addr, sink, nil) + require.NotNil(t, ocr) + require.NoError(t, ocr.Start(context.Background(), componenttest.NewNopHost())) + defer ocr.Shutdown(context.Background()) + + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + require.NoError(t, err) + defer cc.Close() + + for _, ingestionState := range tt.ingestionStates { + if ingestionState.okToIngest { + sink.SetConsumeError(nil) + } else { + sink.SetConsumeError(fmt.Errorf("%q: consumer error", tt.name)) + } + + err = exporter.exportFn(t, cc, req) + + status, ok := status.FromError(err) + require.True(t, ok) + assert.Equal(t, ingestionState.expectedCode, status.Code()) + } + + require.Equal(t, tt.expectedReceivedBatches, len(sink.AllTraces())) + + obsreporttest.CheckReceiverTracesViews(t, exporter.receiverTag, "grpc", int64(tt.expectedReceivedBatches), int64(tt.expectedIngestionBlockedRPCs)) + }) + } + } +} + +func TestGRPCInvalidTLSCredentials(t *testing.T) { + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: "IncorrectTLS", + }, + Protocols: Protocols{ + GRPC: &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: testutil.GetAvailableLocalAddress(t), + Transport: "tcp", + }, + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "willfail", + }, + }, + }, + }, + } + + // TLS is resolved during Creation of the receiver for GRPC. + _, err := createReceiver(cfg, zap.NewNop()) + assert.EqualError(t, err, + `failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither`) +} + +func TestHTTPInvalidTLSCredentials(t *testing.T) { + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: "IncorrectTLS", + }, + Protocols: Protocols{ + HTTP: &confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CertFile: "willfail", + }, + }, + }, + }, + } + + // TLS is resolved during Start for HTTP. + r := newReceiver(t, NewFactory(), cfg, new(consumertest.TracesSink), new(consumertest.MetricsSink)) + assert.EqualError(t, r.Start(context.Background(), componenttest.NewNopHost()), + `failed to load TLS config: for auth via TLS, either both certificate and key must be supplied, or neither`) +} + +func newGRPCReceiver(t *testing.T, name string, endpoint string, tc consumer.TracesConsumer, mc consumer.MetricsConsumer) *otlpReceiver { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.SetName(name) + cfg.GRPC.NetAddr.Endpoint = endpoint + cfg.HTTP = nil + return newReceiver(t, factory, cfg, tc, mc) +} + +func newHTTPReceiver(t *testing.T, endpoint string, tc consumer.TracesConsumer, mc consumer.MetricsConsumer) *otlpReceiver { + factory := NewFactory() + cfg := factory.CreateDefaultConfig().(*Config) + cfg.SetName(otlpReceiverName) + cfg.HTTP.Endpoint = endpoint + cfg.GRPC = nil + return newReceiver(t, factory, cfg, tc, mc) +} + +func newReceiver(t *testing.T, factory component.ReceiverFactory, cfg *Config, tc consumer.TracesConsumer, mc consumer.MetricsConsumer) *otlpReceiver { + r, err := createReceiver(cfg, zap.NewNop()) + require.NoError(t, err) + if tc != nil { + params := component.ReceiverCreateParams{} + _, err := factory.CreateTracesReceiver(context.Background(), params, cfg, tc) + require.NoError(t, err) + } + if mc != nil { + params := component.ReceiverCreateParams{} + _, err := factory.CreateMetricsReceiver(context.Background(), params, cfg, mc) + require.NoError(t, err) + } + return r +} + +func compressGzip(body []byte) (*bytes.Buffer, error) { + var buf bytes.Buffer + + gw := gzip.NewWriter(&buf) + defer gw.Close() + + _, err := gw.Write(body) + if err != nil { + return nil, err + } + + return &buf, nil +} diff --git a/internal/otel_collector/receiver/otlpreceiver/otlphttp.go b/internal/otel_collector/receiver/otlpreceiver/otlphttp.go new file mode 100644 index 00000000000..1f0e14d5171 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/otlphttp.go @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlpreceiver + +import ( + "bytes" + "net/http" + + "github.com/gogo/protobuf/jsonpb" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// xProtobufMarshaler is a Marshaler which wraps runtime.ProtoMarshaller +// and sets ContentType to application/x-protobuf +type xProtobufMarshaler struct { + *runtime.ProtoMarshaller +} + +// ContentType always returns "application/x-protobuf". +func (*xProtobufMarshaler) ContentType() string { + return "application/x-protobuf" +} + +var jsonMarshaller = &jsonpb.Marshaler{} + +// errorHandler encodes the HTTP error message inside a rpc.Status message as required +// by the OTLP protocol. +func errorHandler(w http.ResponseWriter, r *http.Request, errMsg string, statusCode int) { + var ( + msg []byte + s *status.Status + err error + ) + // Pre-computed status with code=Internal to be used in case of a marshaling error. + fallbackMsg := []byte(`{"code": 13, "message": "failed to marshal error message"}`) + fallbackContentType := "application/json" + + if statusCode == http.StatusBadRequest { + s = status.New(codes.InvalidArgument, errMsg) + } else { + s = status.New(codes.Internal, errMsg) + } + + contentType := r.Header.Get("Content-Type") + if contentType == "application/json" { + buf := new(bytes.Buffer) + err = jsonMarshaller.Marshal(buf, s.Proto()) + msg = buf.Bytes() + } else { + msg, err = proto.Marshal(s.Proto()) + } + if err != nil { + msg = fallbackMsg + contentType = fallbackContentType + statusCode = http.StatusInternalServerError + } + + w.Header().Set("Content-Type", contentType) + w.WriteHeader(statusCode) + w.Write(msg) +} diff --git a/internal/otel_collector/receiver/otlpreceiver/testdata/bad_empty_config.yaml b/internal/otel_collector/receiver/otlpreceiver/testdata/bad_empty_config.yaml new file mode 100644 index 00000000000..db0b165a615 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/testdata/bad_empty_config.yaml @@ -0,0 +1,15 @@ +receivers: + otlp: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [otlp] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/otlpreceiver/testdata/bad_no_proto_config.yaml b/internal/otel_collector/receiver/otlpreceiver/testdata/bad_no_proto_config.yaml new file mode 100644 index 00000000000..09731f05939 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/testdata/bad_no_proto_config.yaml @@ -0,0 +1,16 @@ +receivers: + otlp: + protocols: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [otlp] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/otlpreceiver/testdata/bad_proto_config.yaml b/internal/otel_collector/receiver/otlpreceiver/testdata/bad_proto_config.yaml new file mode 100644 index 00000000000..3d79ae12a8c --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/testdata/bad_proto_config.yaml @@ -0,0 +1,18 @@ +receivers: + otlp: + protocols: + thrift: + endpoint: "127.0.0.1:1234" + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [otlp] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/otlpreceiver/testdata/config.yaml b/internal/otel_collector/receiver/otlpreceiver/testdata/config.yaml new file mode 100644 index 00000000000..29ab7bfa108 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/testdata/config.yaml @@ -0,0 +1,95 @@ +receivers: + # The following entry initializes the default OTLP receiver. + # The full name of this receiver is `otlp` and can be referenced in pipelines by 'otlp'. + otlp: + protocols: + grpc: + http: + # The following entry initializes the default OTLP receiver with only gRPC support. + otlp/only_grpc: + protocols: + grpc: + # The following entry initializes the default OTLP receiver with only http support. + otlp/only_http: + protocols: + http: + # The following entry demonstrates configuring the common receiver settings: + # - endpoint + # This configuration is of type 'otlp' and has the name 'customname' with a full name of 'otlp/customname' + # ('/'. To reference this configuration in a pipeline, use the full name `otlp/customname`. + otlp/customname: + protocols: + grpc: + # The receiver will listen on endpoint: "localhost:9090". + endpoint: localhost:9090 + # The following entry configures all of the keep alive settings. These settings are used to configure the receiver. + otlp/keepalive: + protocols: + grpc: + keepalive: + server_parameters: + max_connection_idle: 11s + max_connection_age: 12s + max_connection_age_grace: 13s + time: 30s + timeout: 5s + enforcement_policy: + min_time: 10s + permit_without_stream: true + # The following demonstrates how to set maximum limits on stream, message size and connection idle time. + # Note: The test yaml has demonstrated configuration on a grouped by their structure; however, all of the settings can + # be mix and matched like adding the maximum connection idle setting in this example. + otlp/msg-size-conc-connect-max-idle: + protocols: + grpc: + max_recv_msg_size_mib: 32 + max_concurrent_streams: 16 + read_buffer_size: 1024 + write_buffer_size: 1024 + keepalive: + server_parameters: + max_connection_idle: 10s + # The following entry demonstrates how to specify TLS credentials for the server. + # Note: These files do not exist. If the receiver is started with this configuration, it will fail. + otlp/tlscredentials: + protocols: + grpc: + tls_settings: + cert_file: test.crt + key_file: test.key + http: + tls_settings: + cert_file: test.crt + key_file: test.key + # The following entry demonstrates how to specify a Unix Domain Socket for the server. + otlp/uds: + protocols: + grpc: + transport: unix + endpoint: /tmp/grpc_otlp.sock + http: + # transport: unix + endpoint: /tmp/http_otlp.sock + # The following entry demonstrates how to configure the OTLP receiver to allow Cross-Origin Resource Sharing (CORS). + # Both fully qualified domain names and the use of wildcards are supported. + otlp/cors: + protocols: + http: + cors_allowed_origins: + - https://*.test.com # Wildcard subdomain. Allows domains like https://www.test.com and https://foo.test.com but not https://wwwtest.com. + - https://test.com # Fully qualified domain name. Allows https://test.com only. +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [otlp/customname] + processors: [exampleprocessor] + exporters: [exampleexporter] + metrics: + receivers: [otlp] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/otlpreceiver/testdata/typo_default_proto_config.yaml b/internal/otel_collector/receiver/otlpreceiver/testdata/typo_default_proto_config.yaml new file mode 100644 index 00000000000..15cf1f28599 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/testdata/typo_default_proto_config.yaml @@ -0,0 +1,18 @@ +receivers: + otlp: + protocols: + grpc: + htttp: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [otlp] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/otlpreceiver/trace/otlp.go b/internal/otel_collector/receiver/otlpreceiver/trace/otlp.go new file mode 100644 index 00000000000..3cbb5442e93 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/trace/otlp.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/obsreport" +) + +const ( + dataFormatProtobuf = "protobuf" +) + +// Receiver is the type used to handle spans from OpenTelemetry exporters. +type Receiver struct { + instanceName string + nextConsumer consumer.TracesConsumer +} + +// New creates a new Receiver reference. +func New(instanceName string, nextConsumer consumer.TracesConsumer) *Receiver { + r := &Receiver{ + instanceName: instanceName, + nextConsumer: nextConsumer, + } + + return r +} + +const ( + receiverTagValue = "otlp_trace" + receiverTransport = "grpc" +) + +func (r *Receiver) Export(ctx context.Context, req *collectortrace.ExportTraceServiceRequest) (*collectortrace.ExportTraceServiceResponse, error) { + // We need to ensure that it propagates the receiver name as a tag + ctxWithReceiverName := obsreport.ReceiverContext(ctx, r.instanceName, receiverTransport) + + // Perform backward compatibility conversion of Span Status code according to + // OTLP specification. + // See https://github.com/open-telemetry/opentelemetry-proto/blob/59c488bfb8fb6d0458ad6425758b70259ff4a2bd/opentelemetry/proto/trace/v1/trace.proto#L231 + // + // If code==STATUS_CODE_UNSET then the value of `deprecated_code` is the + // carrier of the overall status according to these rules: + // + // if deprecated_code==DEPRECATED_STATUS_CODE_OK then the receiver MUST interpret + // the overall status to be STATUS_CODE_UNSET. + // + // if deprecated_code!=DEPRECATED_STATUS_CODE_OK then the receiver MUST interpret + // the overall status to be STATUS_CODE_ERROR. + // + // If code!=STATUS_CODE_UNSET then the value of `deprecated_code` MUST be + // ignored, the `code` field is the sole carrier of the status. + for _, rss := range req.ResourceSpans { + for _, ils := range rss.InstrumentationLibrarySpans { + for _, span := range ils.Spans { + if span.Status.Code == otlptrace.Status_STATUS_CODE_UNSET && + span.Status.DeprecatedCode != otlptrace.Status_DEPRECATED_STATUS_CODE_OK { + span.Status.Code = otlptrace.Status_STATUS_CODE_ERROR + } + } + } + } + + td := pdata.TracesFromOtlp(req.ResourceSpans) + err := r.sendToNextConsumer(ctxWithReceiverName, td) + if err != nil { + return nil, err + } + + return &collectortrace.ExportTraceServiceResponse{}, nil +} + +func (r *Receiver) sendToNextConsumer(ctx context.Context, td pdata.Traces) error { + numSpans := td.SpanCount() + if numSpans == 0 { + return nil + } + + if c, ok := client.FromGRPC(ctx); ok { + ctx = client.NewContext(ctx, c) + } + + ctx = obsreport.StartTraceDataReceiveOp(ctx, r.instanceName, receiverTransport) + err := r.nextConsumer.ConsumeTraces(ctx, td) + obsreport.EndTraceDataReceiveOp(ctx, dataFormatProtobuf, numSpans, err) + + return err +} diff --git a/internal/otel_collector/receiver/otlpreceiver/trace/otlp_test.go b/internal/otel_collector/receiver/otlpreceiver/trace/otlp_test.go new file mode 100644 index 00000000000..aef37569e89 --- /dev/null +++ b/internal/otel_collector/receiver/otlpreceiver/trace/otlp_test.go @@ -0,0 +1,288 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/data" + collectortrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/collector/trace/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/testutil" +) + +var _ collectortrace.TraceServiceServer = (*Receiver)(nil) + +func TestExport(t *testing.T) { + // given + + traceSink := new(consumertest.TracesSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, traceSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer traceClientDoneFn() + + // when + + unixnanos := uint64(12578940000000012345) + traceID := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1} + spanID := [8]byte{8, 7, 6, 5, 4, 3, 2, 1} + resourceSpans := []*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + TraceId: data.NewTraceID(traceID), + SpanId: data.NewSpanID(spanID), + Name: "operationB", + Kind: otlptrace.Span_SPAN_KIND_SERVER, + StartTimeUnixNano: unixnanos, + EndTimeUnixNano: unixnanos, + Status: otlptrace.Status{Message: "status-cancelled", Code: otlptrace.Status_STATUS_CODE_ERROR}, + TraceState: "a=text,b=123", + }, + }, + }, + }, + }, + } + + // Keep trace data to compare the test result against it + // Clone needed because OTLP proto XXX_ fields are altered in the GRPC downstream + traceData := pdata.TracesFromOtlp(resourceSpans).Clone() + + req := &collectortrace.ExportTraceServiceRequest{ + ResourceSpans: resourceSpans, + } + + resp, err := traceClient.Export(context.Background(), req) + require.NoError(t, err, "Failed to export trace: %v", err) + require.NotNil(t, resp, "The response is missing") + + // assert + + require.Equal(t, 1, len(traceSink.AllTraces()), "unexpected length: %v", len(traceSink.AllTraces())) + + assert.EqualValues(t, traceData, traceSink.AllTraces()[0]) +} + +func TestExport_EmptyRequest(t *testing.T) { + traceSink := new(consumertest.TracesSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, traceSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer traceClientDoneFn() + + resp, err := traceClient.Export(context.Background(), &collectortrace.ExportTraceServiceRequest{}) + assert.NoError(t, err, "Failed to export trace: %v", err) + assert.NotNil(t, resp, "The response is missing") +} + +func TestExport_ErrorConsumer(t *testing.T) { + traceSink := new(consumertest.TracesSink) + traceSink.SetConsumeError(fmt.Errorf("error")) + + port, doneFn := otlpReceiverOnGRPCServer(t, traceSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer traceClientDoneFn() + + req := &collectortrace.ExportTraceServiceRequest{ + ResourceSpans: []*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + Name: "operationB", + }, + }, + }, + }, + }, + }, + } + + resp, err := traceClient.Export(context.Background(), req) + assert.EqualError(t, err, "rpc error: code = Unknown desc = error") + assert.Nil(t, resp) +} + +func makeTraceServiceClient(port int) (collectortrace.TraceServiceClient, func(), error) { + addr := fmt.Sprintf(":%d", port) + cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + return nil, nil, err + } + + metricsClient := collectortrace.NewTraceServiceClient(cc) + + doneFn := func() { _ = cc.Close() } + return metricsClient, doneFn, nil +} + +func otlpReceiverOnGRPCServer(t *testing.T, tc consumer.TracesConsumer) (int, func()) { + ln, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err) + + doneFnList := []func(){func() { ln.Close() }} + done := func() { + for _, doneFn := range doneFnList { + doneFn() + } + } + + _, port, err := testutil.HostPortFromAddr(ln.Addr()) + if err != nil { + done() + t.Fatalf("Failed to parse host:port from listener address: %s error: %v", ln.Addr(), err) + } + + r := New(receiverTagValue, tc) + require.NoError(t, err) + + // Now run it as a gRPC server + srv := obsreport.GRPCServerWithObservabilityEnabled() + collectortrace.RegisterTraceServiceServer(srv, r) + go func() { + _ = srv.Serve(ln) + }() + + return port, done +} + +func TestDeprecatedStatusCode(t *testing.T) { + traceSink := new(consumertest.TracesSink) + + port, doneFn := otlpReceiverOnGRPCServer(t, traceSink) + defer doneFn() + + traceClient, traceClientDoneFn, err := makeTraceServiceClient(port) + require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err) + defer traceClientDoneFn() + + // See specification for handling status code here: + // https://github.com/open-telemetry/opentelemetry-proto/blob/59c488bfb8fb6d0458ad6425758b70259ff4a2bd/opentelemetry/proto/trace/v1/trace.proto#L231 + tests := []struct { + sendCode otlptrace.Status_StatusCode + sendDeprecatedCode otlptrace.Status_DeprecatedStatusCode + expectedRcvCode otlptrace.Status_StatusCode + }{ + { + // If code==STATUS_CODE_UNSET then the value of `deprecated_code` is the + // carrier of the overall status according to these rules: + // + // if deprecated_code==DEPRECATED_STATUS_CODE_OK then the receiver MUST interpret + // the overall status to be STATUS_CODE_UNSET. + sendCode: otlptrace.Status_STATUS_CODE_UNSET, + sendDeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_OK, + expectedRcvCode: otlptrace.Status_STATUS_CODE_UNSET, + }, + { + // if deprecated_code!=DEPRECATED_STATUS_CODE_OK then the receiver MUST interpret + // the overall status to be STATUS_CODE_ERROR. + sendCode: otlptrace.Status_STATUS_CODE_UNSET, + sendDeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_UNKNOWN_ERROR, + expectedRcvCode: otlptrace.Status_STATUS_CODE_ERROR, + }, + { + // If code!=STATUS_CODE_UNSET then the value of `deprecated_code` MUST be + // ignored, the `code` field is the sole carrier of the status. + sendCode: otlptrace.Status_STATUS_CODE_OK, + sendDeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_OK, + expectedRcvCode: otlptrace.Status_STATUS_CODE_OK, + }, + { + // If code!=STATUS_CODE_UNSET then the value of `deprecated_code` MUST be + // ignored, the `code` field is the sole carrier of the status. + sendCode: otlptrace.Status_STATUS_CODE_OK, + sendDeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_UNKNOWN_ERROR, + expectedRcvCode: otlptrace.Status_STATUS_CODE_OK, + }, + { + // If code!=STATUS_CODE_UNSET then the value of `deprecated_code` MUST be + // ignored, the `code` field is the sole carrier of the status. + sendCode: otlptrace.Status_STATUS_CODE_ERROR, + sendDeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_OK, + expectedRcvCode: otlptrace.Status_STATUS_CODE_ERROR, + }, + { + // If code!=STATUS_CODE_UNSET then the value of `deprecated_code` MUST be + // ignored, the `code` field is the sole carrier of the status. + sendCode: otlptrace.Status_STATUS_CODE_ERROR, + sendDeprecatedCode: otlptrace.Status_DEPRECATED_STATUS_CODE_UNKNOWN_ERROR, + expectedRcvCode: otlptrace.Status_STATUS_CODE_ERROR, + }, + } + + for _, test := range tests { + resourceSpans := []*otlptrace.ResourceSpans{ + { + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + Status: otlptrace.Status{ + Code: test.sendCode, + DeprecatedCode: test.sendDeprecatedCode, + }, + }, + }, + }, + }, + }, + } + + req := &collectortrace.ExportTraceServiceRequest{ + ResourceSpans: resourceSpans, + } + + traceSink.Reset() + + resp, err := traceClient.Export(context.Background(), req) + require.NoError(t, err, "Failed to export trace: %v", err) + require.NotNil(t, resp, "The response is missing") + + require.Equal(t, 1, len(traceSink.AllTraces()), "unexpected length: %v", len(traceSink.AllTraces())) + + rcvdStatus := traceSink.AllTraces()[0].ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).Status() + + // Check that Code is as expected. + assert.EqualValues(t, rcvdStatus.Code(), test.expectedRcvCode) + + // Check that DeprecatedCode is passed as is. + assert.EqualValues(t, rcvdStatus.DeprecatedCode(), test.sendDeprecatedCode) + } +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/DESIGN.md b/internal/otel_collector/receiver/prometheusreceiver/DESIGN.md new file mode 100644 index 00000000000..b46fabda8a9 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/DESIGN.md @@ -0,0 +1,584 @@ +## Design Goals + +### Provide a seamless onboarding experience for users who are already familiar with Prometheus scrape config + +Prometheus has a very powerful config system for user to config how Prometheus +can scrape the metrics data from any application which expose a Prometheus +format metrics endpoint. It provides very useful features like filtering +unwanted metrics, relabeling tags, etc. The original Prometheus receiver of +OpenTelemetry took the approach of using Prometheus' own scraper's source code +as a library to achieve this goal. Overall the idea was great, however, the +original implementation has a lot of glitches, it cannot be fixed by small +patches. This new Prometheus receiver is going to follow the same idea of +leveraging Prometheus sourcecode, with a proper implementation. + +### Map Prometheus metrics to the corresponding OpenTelemetry metrics properly + +Prometheus receiver shall be able to map Prometheus metrics to OpenTelemetry's +proto based metrics, it shall respect the original metric name, value, +timestamp, as well as tags. It doesn't need to provide one-to-one mapping, +since supported metric types are different from the two systems. However, it +shall not drop data. + +### Parity between Prometheus and OpenTelemetry Prometheus exporter + +Prometheus itself can also used as an exporter, that it can expose the metrics +it scrape from other system with its own metrics endpoint, so is OpenTelemetry +service. We shall be able to retain parity from the following two setups: + +1. app -> prometheus -> metric-endpoint +2. app -> otelcol-with-prometheus-receiver -> otelcol-prometheus-exporter-metrics-endpoint + + +## Prometheus Text Format Overview + +Prometheus text format is a line orient format. For each non-empty line, which +not begins with #, is a metric data point with includes a metric name and its +value, which is of float64 type, as well as some optional data such as tags and +timestamp, which is in milliseconds. For lines begin with #, they are either +comments, which need to be filtered, or metadata, which including type hints +and units that are usually indicating the beginning of a new individual metric +or a group of new metrics. More details of Prometheus text format can be found +from its [official +document](https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format). + +### Metric types +Based on this document, Prometheus supports the following 5 types of metrics: +* Counter +* Gauge +* Histogram +* Summary +* Untyped (untyped metrics are converted to `gauge` by default) + +However, this is not the whole story, from the implementation details of +Prometheus scraper, which the receiver based on, it supports a couple more +undocumented metrics types, including: + +* Gaugehistogram +* Info +* Statset + +More details can be found from the +[prometheus text parser source code]( https://github.com/prometheus/prometheus/blob/master/pkg/textparse/interface.go#L82) + +### Metric Grouping + +Other than metric types, the type hint comment and metric grouping are also +important to know in order to parse Prometheus text metrics properly. From any +Prometheus metrics endpoints, metrics are usually grouped together by starting +with a comment section which includes some very important information like type +hints about the metrics, and metrics points of the same group will have the +same metric name but a different set of tag values, for example: + +``` +# HELP container_cpu_load_average_10s Value of container cpu load average over the last 10 seconds. +# TYPE container_cpu_load_average_10s gauge +container_cpu_load_average_10s{id="/",image="",name=""} 0 +container_cpu_load_average_10s{id="/000-metadata",image="",name=""} 0 +container_cpu_load_average_10s{id="/001-sysfs",image="",name=""} 0 +``` + +The above example was taken from an cadvisor metric endpoint, the type hint +tells that the name of this metric group is `container_cpu_load_average_10s` +and it's of `gague` type. Then it follows by some individual metric points +which are of the same metric name. For each individual metric within this +group, they share the same set of tag keys, with unique value sets. + +## Prometheus Metric Scraper Anatomy + +The metrics scraper is a component which is used to scrape remote Prometheus +metric endpoints, it is also the component which Prometheus receiver is based +on. It's important to understand how it works in order to implement the +receiver properly. + +### Major components of Prometheus Scape package + +- **[ScapeManager](https://github.com/prometheus/prometheus/blob/v2.9.2/scrape/manager.go):** +the component which loads the scrape_config, and manage the scraping tasks + +- **[ScrapePool](https://github.com/prometheus/prometheus/blob/d3245f15022551c6fc8281766ea62db4d71e2747/scrape/scrape.go#L154-L439):** +an object which manage scrapes for a sets of targets + +- **[Scraper](https://github.com/prometheus/prometheus/blob/d3245f15022551c6fc8281766ea62db4d71e2747/scrape/scrape.go#L506-L511):** +a http client to fetch data from remote metrics endpoints + +- **[Target](https://github.com/prometheus/prometheus/blob/v2.9.2/scrape/target.go):** +the remote metric endpoint, as well as related relabing settings and other metadata + +- **[TextParser](https://github.com/prometheus/prometheus/tree/v2.9.2/pkg/textparse):** +a DFA style streaming decoder/parser for prometheus text format + +- **[Appendable](https://github.com/prometheus/prometheus/blob/d3245f15022551c6fc8281766ea62db4d71e2747/scrape/manager.go#L37-L39):** +it is used to acquire a storage appender instance at the beginning of each scrapeLoop run + +- **[storage.Appender](https://github.com/prometheus/prometheus/blob/d3245f15022551c6fc8281766ea62db4d71e2747/storage/interface.go#L86-L95):** +an abstraction of the metric storage which can be a filesystem, a database or an remote endpoint...etc. As for OpenTelemetry prometheus receiver, this is +also the interface we need to implement to provide a customized storage appender which is backed by metrics sink. + +- **[ScrapeLoop](https://github.com/prometheus/prometheus/blob/d3245f15022551c6fc8281766ea62db4d71e2747/scrape/scrape.go#L586-L1024):** +the actual scrape pipeline which performs the main scraping and ingestion logic. + +### Prometheus ScrapeLoop workflow explained +Each scraping cycle is trigger by an configured interval, its workflow is as +shown in the flowchart below: + +![ScrapeLoop Flowchart](scrapeloop-flowchart.png) + +It basically does the following things in turn: + + 1. make a http call to fetch data from the binding [target](#target)'s metrics endpoint with [scraper](#scraper) + 2. acquired a [storage appender](#storage-appender) instance with the [Appendable](#appendable) interface + 3. feed the data to a textParser + 4. parse and feed metric data points to storage appender + 5. commit if success or rollback + 6. report task status + +## Implementing Prometheus storage.Appender with metrics sink + +### The storage.Appender interface +As discussed in the previous section, the storage.Appender is the most +important piece of components for us to implement so as to bring the two worlds +together. It has a very simple interface which is defined below: +```go +type Appender interface { + Add(l labels.Labels, t int64, v float64) (uint64, error) + + + AddFast(l labels.Labels, ref uint64, t int64, v float64) error + + + // Commit submits the collected samples and purges the batch. + Commit() error + + + Rollback() error +} +``` + +*Note: the above code belongs to the Prometheus project, its license can be found [here](https://github.com/prometheus/prometheus/blob/v2.9.2/LICENSE)* + +One can see that the interface is very simple, it only has 4 methods: `Add`, +`AddFast`, `Commit` and `Rollback`. The last two methods are easy to +understand: `Commit` is called when the processing of the scraped page is +completed and success, whereas `Rollback` is called if error occurs in between +the process. + +However for the two methods starting with 'Add', there's no document on the +Prometheus project for how they should be used. By examining the scrapeLoop +source code, as well as some storage.Appender implementations. It indicates +that the first method `Add` is always used for the first time when a unique +metrics, which means the combination of metric name and its tags are unique, is +seen for the first time. The `Add` method can return a non zero reference +number, then the scrapeLoop can cache this number with the metric's uniq +signature. The next time, such as the next scrape cycle of the same target, +when the metric is seen again by matching its signature, it will call the +`AddFast` method with the cached reference number. This reference number might +make sense to databases which has unique key as numbers, however, in our use +case, it's not necessary, thus we can always return 0 ref number from the `Add` +method to skip this caching mechanism. + +### Challenges and solutions +Even though the definition of this interface is very simple, however, to +implement it properly is a bit challenging, given that every time the +Add/AddFast method is called, it only provides the information about the +current data point, the context of what metric group this data point belonging +to is not provided, we have to keep track of it internally within the appender. +And this is not the whole story, there are a couple other issues we need to +address, including: + +1. Have a way to link the Target with the current appender instance + +The labels provided to the Add/AddFast methods dose not include some target +specified information such as `job name` which is important construct the [Node +proto](https://github.com/census-instrumentation/opencensus-proto/blob/e2601ef16f8a085a69d94ace5133f97438f8945f/src/opencensus/proto/agent/common/v1/common.proto#L36-L51) +object of OpenTelemetry. The target object is not accessible from the Appender +interface, however, we can get it from the ScrapeManager, when designing the +appender, we need to have a way to inject the binding target into the appender +instance. + +2. Group metrics from the same family together + +In OpenTelemetry, metric points of the same name are usually grouped together +as one timeseries but different data points. It's important for the appender to +keep track of the metric family changes, and group metrics of the same family +together Keep in mind that the Add/AddFast method is operated in a streaming +manner, ScrapeLoop does not provide any direct hints on metric name change, the +appender itself need to keep track of it. It's also important to know that for +some special types such as `histogram` and `summary`, not all the data points +have the same name, there are some special metric points has postfix like +`_sum` and `_count`, we need to handle this properly, and do not consider this +is a metric family change. + +3. Group complex metrics such as histogram together in proper order + +In Prometheus, a single aggregated type of metric data such as `histogram` and +`summary` is represent by multiple metric data points, such as buckets and +quantiles as well as the additional `_sum` and `_count` data. ScrapeLoop will +feed them into the appender individually. The appender needs to have a way to +bundle them together to transform them into a single Metric Datapoint Proto +object. + +4. Tags need to be handled carefully + +ScrapeLoop strips out any tag with empty value, however, in OpenTelemetry, the +tag keys is stored separately, we need to able to get all the possible tag keys +of the same metric family before committing the metric family to the sink. + +5. StartTimestamp and values of metrics of cumulative types + +In OpenTelemetry, every metrics of cumulative type is required to have a +StartTimestamp, which records when a metric is first recorded, however, +Prometheus dose not provide such data. One of the solutions to tackle this +problem is to cache the first observed value of these metrics as well as the +timestamp, then for any subsequent data of the same metric, use the cached +timestamp as StartTimestamp and the delta with the first value as value. +However, metrics can come and go, or the remote server can restart at any given +time, the receiver also needs to take care of issues such as a new value is +smaller than the previous seen value, by considering it as a metrics with new +StartTime. + +## Prometheus Metric to OpenTelemetry Metric Proto Mapping + +### Target as Node +The Target of Prometheus is defined by the scrape_config, it has the +information like `hostname` of the remote service, and a user defined `job +name` which can be used as the service name. These two piece of information +makes it a great fit to map it into the `Node` proto of the OpenTelemetry +MetricsData type, as shown below: + +```go +type MetricsData struct { + Node *commonpb.Node + Resource *resourcepb.Resource + Metrics []*metricspb.Metric +} +``` + +The scrape page as whole also can be fit into the above `MetricsData` data +structure, and all the metrics data points can be stored with the `Metrics` +array. We will explain the mappings of individual metric types in the following +couple sections + +### Metric Value Mapping + In OpenTelemetry, metrics value types can be either `int64` or `float64`, + while in Prometheus the value can be safely assumed it's always `float64` + based on the [Prometheus Text Format + Document](https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-details) + as quoted below: + +> value is a float represented as required by Go's ParseFloat() function. +> In addition to standard numerical values, Nan, +Inf, and -Inf are valid +> values representing not a number, positive infinity, and negative infinity, +> respectively. + +It will make sense for us to stick with this data type as much as possible +across all metrics types. + +### Counter +Counter as described in the [Prometheus Metric Types +Document](https://prometheus.io/docs/concepts/metric_types/#counter), + +> is a cumulative metric that represents a single monotonically increasing +> counter whose value can only increase or be reset to zero on restart. + +It is one of the most simple metric types found in both systems, however, it is +a cumulative type of metric. Consider what happens when we have two consecutive +scrapes from a target, with the first one as shown below: +``` +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 1027 +http_requests_total{method="post",code="400"} 3 +``` + +and the 2nd one: +``` +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 1028 +http_requests_total{method="post",code="400"} 5 +``` + +The Prometheus Receiver will only produce one Metric from the 2nd scrape and +subsequent ones if any. The 1st scrape, however, is stored as metadata to +calcualate a delta from. + +The output of the 2nd scrape is as shown below: +```go +metrics := []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_requests_total", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "method"}, {Key: "code"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: startTimestamp, + LabelValues: []*metricspb.LabelValue{{Value: "post", HasValue: true}, {Value: "200", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_DoubleValue{DoubleValue: 1.0}}, + }, + }, + { + StartTimestamp: startTimestamp, + LabelValues: []*metricspb.LabelValue{{Value: "post", HasValue: false}, {Value: "400", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_DoubleValue{DoubleValue: 2.0}}, + }, + }, + }, + }, +} +``` + +*Note: `startTimestamp` is the timestamp cached from the first scrape, `currentTimestamp` is the timestamp of the current scrape* + + +### Gauge +Gauge, as described in the [Prometheus Metric Types Document](https://prometheus.io/docs/concepts/metric_types/#guage), +> is a metric that represents a single numerical value that can arbitrarily go up and down + +``` +# HELP gauge_test some test gauges. +# TYPE gauge_test gague +gauge_test{id="1",foo="bar"} 1.0 +gauge_test{id="2",foo=""} 2.0 + +``` + +A major different between Gauges of Prometheus and OpenTelemetry are the value +types. In Prometheus, as mentioned earlier, all values can be considered as +float type, however, in OpenTelemetry, Gauges can either be `Int64` or +`Double`. To make the transformation easier, we always assume the data type is +`Double`. + +The corresponding OpenTelemetry Metric of the above examples will be: +```go +metrics := []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "gauge_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "id"}, {Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: nil, + LabelValues: []*metricspb.LabelValue{{Value: "1", HasValue: true}, {Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_DoubleValue{DoubleValue: 1.0}}, + }, + }, + { + StartTimestamp: nil, + LabelValues: []*metricspb.LabelValue{{Value: "2", HasValue: true}, {Value: "", HasValue: false}}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_DoubleValue{DoubleValue: 2.0}}, + }, + }, + }, + }, +} +``` + +### Histogram +Histogram is a complex data type, in Prometheus, it uses multiple data points +to represent a single histogram. Its description can be found from: [Prometheus +Histogram](https://prometheus.io/docs/concepts/metric_types/#histogram). + +Similar to counter, histogram is also a cumulative type metric, thus only the +2nd and subsequent scrapes can produce a metric for OpenTelemetry, with the +first scrape stored as metadata. + +An example of histogram with first scrape response: +``` +# HELP hist_test This is my histogram vec +# TYPE hist_test histogram +hist_test_bucket{t1="1",,le="10.0"} 1.0 +hist_test_bucket{t1="1",le="20.0"} 3.0 +hist_test_bucket{t1="1",le="+inf"} 10.0 +hist_test_sum{t1="1"} 100.0 +hist_test_count{t1="1"} 10.0 +hist_test_bucket{t1="2",,le="10.0"} 10.0 +hist_test_bucket{t1="2",le="20.0"} 30.0 +hist_test_bucket{t1="2",le="+inf"} 100.0 +hist_test_sum{t1="2"} 10000.0 +hist_test_count{t1="2"} 100.0 + +``` + +And a subsequent 2nd scrape response: +``` +# HELP hist_test This is my histogram vec +# TYPE hist_test histogram +hist_test_bucket{t1="1",,le="10.0"} 2.0 +hist_test_bucket{t1="1",le="20.0"} 6.0 +hist_test_bucket{t1="1",le="+inf"} 13.0 +hist_test_sum{t1="1"} 150.0 +hist_test_count{t1="1"} 13.0 +hist_test_bucket{t1="2",,le="10.0"} 10.0 +hist_test_bucket{t1="2",le="20.0"} 30.0 +hist_test_bucket{t1="2",le="+inf"} 100.0 +hist_test_sum{t1="2"} 10000.0 +hist_test_count{t1="2"} 100.0 + +``` + +Its corresponding OpenTelemetry metrics will be: +```go +metrics := []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{{Key: "t1"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: startTimestamp, + LabelValues: []*metricspb.LabelValue{{Value: "1", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 3, + Sum: 50.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 2}, {Count: 0}}, + }}}, + }, + }, + { + StartTimestamp: startTimestamp, + LabelValues: []*metricspb.LabelValue{{Value: "2", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 0, + Sum: 0.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 0}, {Count: 0}, {Count: 0}}, + }}}, + }, + }, + }, + }, +} + +``` + +There's an important difference between Prometheus bucket and OpenTelemetry +bucket that, bucket counts from Prometheus are cumulative, to transform this +into OpenTelemetry format, one needs to apply the following formula: + +``` +CurrentOCBucketVlaue = CurrentPrometheusBucketValue - PrevPrometheusBucketValue +``` + +OpenTelemetry does not use `+inf` as bound, one needs to remove it to generate +the Bounds of the OpenTelemetry Bounds. + +Other than that, the `SumOfSquaredDeviation`, which is required by +OpenTelemetry format for histogram, is not provided by Prometheus. We have to +set this value to `0` instead. + +### Gaugehistogram + +This is an undocumented data type, that's not currently supported. + +### Summary + +Same as histogram, summary is also a complex metric type which is represent by +multiple data points. A detailed description can be found from [Prometheus +Summary](https://prometheus.io/docs/concepts/metric_types/#summary) + +The sum and count from Summary is also cumulative, however, the quantiles are +not. The receiver will still consider the first scrape as metadata, and won't +produce an output. For any subsequent scrapes, the count and sum will be deltas +from the first scrape, while the quantiles are left as it is. + +For the following two scrapes, with the first one: + +``` +# HELP go_gc_duration_seconds A summary of the GC invocation durations. +# TYPE go_gc_duration_seconds summary +go_gc_duration_seconds{quantile="0"} 0.0001271 +go_gc_duration_seconds{quantile="0.25"} 0.0002455 +go_gc_duration_seconds{quantile="0.5"} 0.0002904 +go_gc_duration_seconds{quantile="0.75"} 0.0003426 +go_gc_duration_seconds{quantile="1"} 0.0023638 +go_gc_duration_seconds_sum 17.391350544 +go_gc_duration_seconds_count 52489 +``` + +And the 2nd one: +``` +# HELP go_gc_duration_seconds A summary of the GC invocation durations. +# TYPE go_gc_duration_seconds summary +go_gc_duration_seconds{quantile="0"} 0.0001271 +go_gc_duration_seconds{quantile="0.25"} 0.0002455 +go_gc_duration_seconds{quantile="0.5"} 0.0002904 +go_gc_duration_seconds{quantile="0.75"} 0.0003426 +go_gc_duration_seconds{quantile="1"} 0.0023639 +go_gc_duration_seconds_sum 17.491350544 +go_gc_duration_seconds_count 52490 +``` + +The corresponding OpenTelemetry metrics is as shown below: + +```go +metrics := []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_gc_duration_seconds", + Type: metricspb.MetricDescriptor_SUMMARY, + LabelKeys: []*metricspb.LabelKey{}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: startTimestamp, + LabelValues: []*metricspb.LabelValue{}, + Points: []*metricspb.Point{ + {Timestamp: currentTimestamp, Value: &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrappers.DoubleValue{Value: 0.1}, + Count: &wrappers.Int64Value{Value: 1}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ + {Percentile: 0.0, Value: 0.0001271}, + {Percentile: 25.0, Value: 0.0002455}, + {Percentile: 50.0, Value: 0.0002904}, + {Percentile: 75.0, Value: 0.0003426}, + {Percentile: 100.0, Value: 0.0023639}, + }, + }}}}, + }, + }, + }, + }, +} + +``` + +There's also some differences between the two systems. One difference is that +Prometheus uses `quantile`, while OpenTelemetry uses `percentile`. +Additionally, OpenTelemetry has optional values for `Sum` and `Count` of a +snapshot, however, they are not provided by Prometheus, and `nil` will be used +for these values. + +Other than that, in some Prometheus client implementations, such as the Python +version, Summary is allowed to have no quantiles, in which case the receiver +will produce an OpenTelemetry Summary with Snapshot set to `nil`. + +### Others + +For any other Prometheus metrics types, they will be transformed into the +OpenTelemetry [Gauge](#gague) type. diff --git a/internal/otel_collector/receiver/prometheusreceiver/README.md b/internal/otel_collector/receiver/prometheusreceiver/README.md new file mode 100644 index 00000000000..18813909772 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/README.md @@ -0,0 +1,62 @@ +# Prometheus Receiver + +Receives metric data in [Prometheus](https://prometheus.io/) format. See the +[Design](DESIGN.md) for additional information on this receiver. + +Supported pipeline types: metrics + +## ⚠️ Warning + +Note: This component is currently work in progress. It has several limitations +and please don't use it if the following limitations is a concern: + +* Collector cannot auto-scale the scraping yet when multiple replicas of the + collector is run. +* When running multiple replicas of the collector with the same config, it will + scrape the targets multiple times. +* Users need to configure each replica with different scraping configuration + if they want to manually shard the scraping. +* The Prometheus receiver is a stateful component. + +## Getting Started + +This receiver is a drop-in replacement for getting Prometheus to scrape your +services. It supports the full set of Prometheus configuration, including +service discovery. Just like you would write in a YAML configuration file +before starting Prometheus, such as with: + +```shell +prometheus --config.file=prom.yaml +``` + +You can copy and paste that same configuration under: + +```yaml +receivers: + prometheus: + config: +``` + +For example: + +```yaml +receivers: + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 5s + static_configs: + - targets: ['0.0.0.0:8888'] + - job_name: k8s + kubernetes_sd_configs: + - role: pod + relabel_configs: + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] + regex: "true" + action: keep + metric_relabel_configs: + - source_labels: [__name__] + regex: "(request_duration_seconds.*|response_duration_seconds.*)" + action: keep +``` diff --git a/internal/otel_collector/receiver/prometheusreceiver/config.go b/internal/otel_collector/receiver/prometheusreceiver/config.go new file mode 100644 index 00000000000..992ceb59269 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/config.go @@ -0,0 +1,38 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusreceiver + +import ( + "time" + + "github.com/prometheus/prometheus/config" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for Prometheus receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` + PrometheusConfig *config.Config `mapstructure:"-"` + BufferPeriod time.Duration `mapstructure:"buffer_period"` + BufferCount int `mapstructure:"buffer_count"` + UseStartTimeMetric bool `mapstructure:"use_start_time_metric"` + StartTimeMetricRegex string `mapstructure:"start_time_metric_regex"` + + // ConfigPlaceholder is just an entry to make the configuration pass a check + // that requires that all keys present in the config actually exist on the + // structure, ie.: it will error if an unknown key is present. + ConfigPlaceholder interface{} `mapstructure:"config"` +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/config_test.go b/internal/otel_collector/receiver/prometheusreceiver/config_test.go new file mode 100644 index 00000000000..7583ff3a82e --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/config_test.go @@ -0,0 +1,146 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusreceiver + +import ( + "fmt" + "os" + "path" + "testing" + "time" + + "github.com/prometheus/prometheus/discovery/kubernetes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 2) + + r0 := cfg.Receivers["prometheus"] + assert.Equal(t, r0, factory.CreateDefaultConfig()) + + r1 := cfg.Receivers["prometheus/customname"].(*Config) + assert.Equal(t, r1.ReceiverSettings, + configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "prometheus/customname", + }) + assert.Equal(t, r1.PrometheusConfig.ScrapeConfigs[0].JobName, "demo") + assert.Equal(t, time.Duration(r1.PrometheusConfig.ScrapeConfigs[0].ScrapeInterval), 5*time.Second) + assert.Equal(t, r1.UseStartTimeMetric, true) + assert.Equal(t, r1.StartTimeMetricRegex, "^(.+_)*process_start_time_seconds$") +} + +func TestLoadConfigWithEnvVar(t *testing.T) { + const jobname = "JobName" + const jobnamevar = "JOBNAME" + os.Setenv(jobnamevar, jobname) + + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config_env.yaml"), factories) + require.NoError(t, err) + require.NotNil(t, cfg) + + r := cfg.Receivers["prometheus"].(*Config) + assert.Equal(t, r.ReceiverSettings, + configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "prometheus", + }) + assert.Equal(t, r.PrometheusConfig.ScrapeConfigs[0].JobName, jobname) + os.Unsetenv(jobnamevar) +} + +func TestLoadConfigK8s(t *testing.T) { + const node = "node1" + const nodenamevar = "NODE_NAME" + os.Setenv(nodenamevar, node) + defer os.Unsetenv(nodenamevar) + + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config_k8s.yaml"), factories) + require.NoError(t, err) + require.NotNil(t, cfg) + + r := cfg.Receivers["prometheus"].(*Config) + assert.Equal(t, r.ReceiverSettings, + configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "prometheus", + }) + + scrapeConfig := r.PrometheusConfig.ScrapeConfigs[0] + kubeSDConfig := scrapeConfig.ServiceDiscoveryConfigs[0].(*kubernetes.SDConfig) + assert.Equal(t, + kubeSDConfig.Selectors[0].Field, + fmt.Sprintf("spec.nodeName=%s", node)) + assert.Equal(t, + scrapeConfig.RelabelConfigs[1].Replacement, + "$1:$2") +} + +func TestLoadConfigFailsOnUnknownSection(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile( + t, + path.Join(".", "testdata", "invalid-config-section.yaml"), factories) + + require.Error(t, err) + require.Nil(t, cfg) +} + +// As one of the config parameters is consuming prometheus +// configuration as a subkey, ensure that invalid configuration +// within the subkey will also raise an error. +func TestLoadConfigFailsOnUnknownPrometheusSection(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile( + t, + path.Join(".", "testdata", "invalid-config-prometheus-section.yaml"), factories) + + require.Error(t, err) + require.Nil(t, cfg) +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/doc.go b/internal/otel_collector/receiver/prometheusreceiver/doc.go new file mode 100644 index 00000000000..cc269dc5421 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package prometheusreceiver has the logic for scraping Prometheus metrics from +// already instrumented applications and then passing them onto a metricsink instance. +package prometheusreceiver diff --git a/internal/otel_collector/receiver/prometheusreceiver/factory.go b/internal/otel_collector/receiver/prometheusreceiver/factory.go new file mode 100644 index 00000000000..46890fa382c --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/factory.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusreceiver + +import ( + "context" + "errors" + "fmt" + + _ "github.com/prometheus/prometheus/discovery/install" // init() of this package registers service discovery impl. + "github.com/spf13/viper" + "gopkg.in/yaml.v2" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +// This file implements config for Prometheus receiver. + +const ( + // The value of "type" key in configuration. + typeStr = "prometheus" + + // The key for Prometheus scraping configs. + prometheusConfigKey = "config" +) + +var ( + errNilScrapeConfig = errors.New("expecting a non-nil ScrapeConfig") +) + +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithMetrics(createMetricsReceiver), + receiverhelper.WithCustomUnmarshaler(customUnmarshaler)) +} + +func customUnmarshaler(componentViperSection *viper.Viper, intoCfg interface{}) error { + if componentViperSection == nil { + return nil + } + // We need custom unmarshaling because prometheus "config" subkey defines its own + // YAML unmarshaling routines so we need to do it explicitly. + + err := componentViperSection.UnmarshalExact(intoCfg) + if err != nil { + return fmt.Errorf("prometheus receiver failed to parse config: %s", err) + } + + // Unmarshal prometheus's config values. Since prometheus uses `yaml` tags, so use `yaml`. + if !componentViperSection.IsSet(prometheusConfigKey) { + return nil + } + promCfgMap := componentViperSection.Sub(prometheusConfigKey).AllSettings() + out, err := yaml.Marshal(promCfgMap) + if err != nil { + return fmt.Errorf("prometheus receiver failed to marshal config to yaml: %s", err) + } + config := intoCfg.(*Config) + + err = yaml.UnmarshalStrict(out, &config.PrometheusConfig) + if err != nil { + return fmt.Errorf("prometheus receiver failed to unmarshal yaml to prometheus config: %s", err) + } + if len(config.PrometheusConfig.ScrapeConfigs) == 0 { + return errNilScrapeConfig + } + return nil +} + +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + } +} + +func createMetricsReceiver( + _ context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.MetricsConsumer, +) (component.MetricsReceiver, error) { + config := cfg.(*Config) + if config.PrometheusConfig == nil || len(config.PrometheusConfig.ScrapeConfigs) == 0 { + return nil, errNilScrapeConfig + } + return newPrometheusReceiver(params.Logger, config, nextConsumer), nil +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/factory_test.go b/internal/otel_collector/receiver/prometheusreceiver/factory_test.go new file mode 100644 index 00000000000..557c1265de2 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/factory_test.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusreceiver + +import ( + "context" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + cfg := createDefaultConfig() + + // The default config does not provide scrape_config so we expect that metrics receiver + // creation must also fail. + creationParams := component.ReceiverCreateParams{Logger: zap.NewNop()} + mReceiver, err := createMetricsReceiver(context.Background(), creationParams, cfg, nil) + assert.Equal(t, err, errNilScrapeConfig) + assert.Nil(t, mReceiver) +} + +func TestFactoryCanParseServiceDiscoveryConfigs(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + _, err = configtest.LoadConfigFile(t, path.Join(".", "testdata", "config_sd.yaml"), factories) + + assert.NoError(t, err) +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/internal_test.go b/internal/otel_collector/receiver/prometheusreceiver/internal/internal_test.go new file mode 100644 index 00000000000..14da9af4b8e --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/internal_test.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/scrape" + "go.uber.org/zap" +) + +// test helpers + +var testLogger *zap.Logger + +func init() { + zl, _ := zap.NewDevelopment() + testLogger = zl +} + +type mockMetadataCache struct { + data map[string]scrape.MetricMetadata +} + +func newMockMetadataCache(data map[string]scrape.MetricMetadata) *mockMetadataCache { + return &mockMetadataCache{data: data} +} + +func (m *mockMetadataCache) Metadata(metricName string) (scrape.MetricMetadata, bool) { + mm, ok := m.data[metricName] + return mm, ok +} + +func (m *mockMetadataCache) SharedLabels() labels.Labels { + return labels.FromStrings("__scheme__", "http") +} + +type mockScrapeManager struct { + targets map[string][]*scrape.Target +} + +func (sm *mockScrapeManager) TargetsAll() map[string][]*scrape.Target { + return sm.targets +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/logger.go b/internal/otel_collector/receiver/prometheusreceiver/internal/logger.go new file mode 100644 index 00000000000..f827e85245c --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/logger.go @@ -0,0 +1,136 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + gokitLog "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "go.uber.org/zap" +) + +const ( + levelKey = "level" + msgKey = "msg" +) + +// NewZapToGokitLogAdapter create an adapter for zap.Logger to gokitLog.Logger +func NewZapToGokitLogAdapter(logger *zap.Logger) gokitLog.Logger { + // need to skip two levels in order to get the correct caller + // one for this method, the other for gokitLog + logger = logger.WithOptions(zap.AddCallerSkip(2)) + return &zapToGokitLogAdapter{l: logger.Sugar()} +} + +type zapToGokitLogAdapter struct { + l *zap.SugaredLogger +} + +type logData struct { + level level.Value + msg string + otherFields []interface{} +} + +func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { + // expecting key value pairs, the number of items need to be even + if len(keyvals)%2 == 0 { + // Extract log level and message and log them using corresponding zap function + ld := extractLogData(keyvals) + logFunc := levelToFunc(w.l, ld.level) + logFunc(ld.msg, ld.otherFields...) + } else { + // in case something goes wrong + w.l.Info(keyvals...) + } + return nil +} + +func extractLogData(keyvals []interface{}) *logData { + lvl := level.InfoValue() // default + msg := "" + + other := make([]interface{}, 0, len(keyvals)) + for i := 0; i < len(keyvals); i += 2 { + key := keyvals[i] + val := keyvals[i+1] + + if l, ok := matchLogLevel(key, val); ok { + lvl = l + continue + } + + if m, ok := matchLogMessage(key, val); ok { + msg = m + continue + } + + other = append(other, key, val) + } + + return &logData{ + level: lvl, + msg: msg, + otherFields: other, + } +} + +// check if a given key-value pair represents go-kit log message and return it +func matchLogMessage(key interface{}, val interface{}) (string, bool) { + strKey, ok := key.(string) + if !ok || strKey != msgKey { + return "", false + } + + msg, ok := val.(string) + if !ok { + return "", false + } + + return msg, true +} + +// check if a given key-value pair represents go-kit log level and return it +func matchLogLevel(key interface{}, val interface{}) (level.Value, bool) { + strKey, ok := key.(string) + if !ok || strKey != levelKey { + return nil, false + } + + levelVal, ok := val.(level.Value) + if !ok { + return nil, false + } + + return levelVal, true +} + +// find a matching zap logging function to be used for a given level +func levelToFunc(logger *zap.SugaredLogger, lvl level.Value) func(string, ...interface{}) { + switch lvl { + case level.DebugValue(): + return logger.Debugw + case level.InfoValue(): + return logger.Infow + case level.WarnValue(): + return logger.Warnw + case level.ErrorValue(): + return logger.Errorw + } + + // default + return logger.Infof +} + +var _ gokitLog.Logger = (*zapToGokitLogAdapter)(nil) diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/logger_test.go b/internal/otel_collector/receiver/prometheusreceiver/internal/logger_test.go new file mode 100644 index 00000000000..2fc8dbda896 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/logger_test.go @@ -0,0 +1,197 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + + "github.com/go-kit/kit/log/level" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func TestLog(t *testing.T) { + tcs := []struct { + name string + input []interface{} + wantLevel zapcore.Level + wantMessage string + }{ + { + name: "Starting provider", + input: []interface{}{ + "level", + level.DebugValue(), + "msg", + "Starting provider", + "provider", + "string/0", + "subs", + "[target1]", + }, + wantLevel: zapcore.DebugLevel, + wantMessage: "Starting provider", + }, + { + name: "Scrape failed", + input: []interface{}{ + "level", + level.ErrorValue(), + "scrape_pool", + "target1", + "msg", + "Scrape failed", + "err", + "server returned HTTP status 500 Internal Server Error", + }, + wantLevel: zapcore.ErrorLevel, + wantMessage: "Scrape failed", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + conf := zap.NewProductionConfig() + conf.Level.SetLevel(zapcore.DebugLevel) + + // capture zap log entry + var entry zapcore.Entry + h := func(e zapcore.Entry) error { + entry = e + return nil + } + + logger, err := conf.Build(zap.Hooks(h)) + require.NoError(t, err) + + adapter := NewZapToGokitLogAdapter(logger) + err = adapter.Log(tc.input...) + require.NoError(t, err) + + assert.Equal(t, tc.wantLevel, entry.Level) + assert.Equal(t, tc.wantMessage, entry.Message) + }) + } +} + +func TestExtractLogData(t *testing.T) { + tcs := []struct { + name string + input []interface{} + wantLevel level.Value + wantMessage string + wantOutput []interface{} + }{ + { + name: "nil fields", + input: nil, + wantLevel: level.InfoValue(), // Default + wantMessage: "", + wantOutput: []interface{}{}, + }, + { + name: "empty fields", + input: []interface{}{}, + wantLevel: level.InfoValue(), // Default + wantMessage: "", + wantOutput: []interface{}{}, + }, + { + name: "info level", + input: []interface{}{ + "level", + level.InfoValue(), + }, + wantLevel: level.InfoValue(), + wantMessage: "", + wantOutput: []interface{}{}, + }, + { + name: "warn level", + input: []interface{}{ + "level", + level.WarnValue(), + }, + wantLevel: level.WarnValue(), + wantMessage: "", + wantOutput: []interface{}{}, + }, + { + name: "error level", + input: []interface{}{ + "level", + level.ErrorValue(), + }, + wantLevel: level.ErrorValue(), + wantMessage: "", + wantOutput: []interface{}{}, + }, + { + name: "debug level + extra fields", + input: []interface{}{ + "timestamp", + 1596604719, + "level", + level.DebugValue(), + "msg", + "http client error", + }, + wantLevel: level.DebugValue(), + wantMessage: "http client error", + wantOutput: []interface{}{ + "timestamp", + 1596604719, + }, + }, + { + name: "missing level field", + input: []interface{}{ + "timestamp", + 1596604719, + "msg", + "http client error", + }, + wantLevel: level.InfoValue(), // Default + wantMessage: "http client error", + wantOutput: []interface{}{ + "timestamp", + 1596604719, + }, + }, + { + name: "invalid level type", + input: []interface{}{ + "level", + "warn", // String is not recognized + }, + wantLevel: level.InfoValue(), // Default + wantOutput: []interface{}{ + "level", + "warn", // Field is preserved + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ld := extractLogData(tc.input) + assert.Equal(t, tc.wantLevel, ld.level) + assert.Equal(t, tc.wantMessage, ld.msg) + assert.Equal(t, tc.wantOutput, ld.otherFields) + }) + } +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/metadata.go b/internal/otel_collector/receiver/prometheusreceiver/internal/metadata.go new file mode 100644 index 00000000000..e5b4ccd20de --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/metadata.go @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "errors" + + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/scrape" +) + +// MetadataCache is an adapter to prometheus' scrape.Target and provide only the functionality which is needed +type MetadataCache interface { + Metadata(metricName string) (scrape.MetricMetadata, bool) + SharedLabels() labels.Labels +} + +type ScrapeManager interface { + TargetsAll() map[string][]*scrape.Target +} + +type metadataService struct { + sm ScrapeManager +} + +func (s *metadataService) Get(job, instance string) (MetadataCache, error) { + targetGroup, ok := s.sm.TargetsAll()[job] + if !ok { + return nil, errors.New("unable to find a target group with job=" + job) + } + + // from the same targetGroup, instance is not going to be duplicated + for _, target := range targetGroup { + if target.Labels().Get(model.InstanceLabel) == instance { + return &mCache{target}, nil + } + } + + return nil, errors.New("unable to find a target with job=" + job + ", and instance=" + instance) +} + +// adapter to get metadata from scrape.Target +type mCache struct { + t *scrape.Target +} + +func (m *mCache) Metadata(metricName string) (scrape.MetricMetadata, bool) { + return m.t.Metadata(metricName) +} + +func (m *mCache) SharedLabels() labels.Labels { + return m.t.DiscoveredLabels() +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/metricfamily.go b/internal/otel_collector/receiver/prometheusreceiver/internal/metricfamily.go new file mode 100644 index 00000000000..2ffe19b6adc --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/metricfamily.go @@ -0,0 +1,373 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "sort" + "strings" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/pkg/textparse" + "github.com/prometheus/prometheus/scrape" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// MetricFamily is unit which is corresponding to the metrics items which shared the same TYPE/UNIT/... metadata from +// a single scrape. +type MetricFamily interface { + Add(metricName string, ls labels.Labels, t int64, v float64) error + IsSameFamily(metricName string) bool + ToMetric() (*metricspb.Metric, int, int) +} + +type metricFamily struct { + name string + mtype metricspb.MetricDescriptor_Type + mc MetadataCache + droppedTimeseries int + labelKeys map[string]bool + labelKeysOrdered []string + metadata *scrape.MetricMetadata + groupOrders map[string]int + groups map[string]*metricGroup +} + +func newMetricFamily(metricName string, mc MetadataCache) MetricFamily { + familyName := normalizeMetricName(metricName) + + // lookup metadata based on familyName + metadata, ok := mc.Metadata(familyName) + if !ok && metricName != familyName { + // use the original metricName as metricFamily + familyName = metricName + // perform a 2nd lookup with the original metric name. it can happen if there's a metric which is not histogram + // or summary, but ends with one of those _count/_sum suffixes + metadata, ok = mc.Metadata(metricName) + // still not found, this can happen when metric has no TYPE HINT + if !ok { + metadata.Metric = familyName + metadata.Type = textparse.MetricTypeUnknown + } + } + + return &metricFamily{ + name: familyName, + mtype: convToOCAMetricType(metadata.Type), + mc: mc, + droppedTimeseries: 0, + labelKeys: make(map[string]bool), + labelKeysOrdered: make([]string, 0), + metadata: &metadata, + groupOrders: make(map[string]int), + groups: make(map[string]*metricGroup), + } +} + +func (mf *metricFamily) IsSameFamily(metricName string) bool { + // trim known suffix if necessary + familyName := normalizeMetricName(metricName) + return mf.name == familyName || familyName != metricName && mf.name == metricName +} + +// updateLabelKeys is used to store all the label keys of a same metric family in observed order. since prometheus +// receiver removes any label with empty value before feeding it to an appender, in order to figure out all the labels +// from the same metric family we will need to keep track of what labels have ever been observed. +func (mf *metricFamily) updateLabelKeys(ls labels.Labels) { + for _, l := range ls { + if isUsefulLabel(mf.mtype, l.Name) { + if _, ok := mf.labelKeys[l.Name]; !ok { + mf.labelKeys[l.Name] = true + // use insertion sort to maintain order + i := sort.SearchStrings(mf.labelKeysOrdered, l.Name) + labelKeys := append(mf.labelKeysOrdered, "") + copy(labelKeys[i+1:], labelKeys[i:]) + labelKeys[i] = l.Name + mf.labelKeysOrdered = labelKeys + } + } + } +} + +func (mf *metricFamily) isCumulativeType() bool { + return mf.mtype == metricspb.MetricDescriptor_CUMULATIVE_DOUBLE || + mf.mtype == metricspb.MetricDescriptor_CUMULATIVE_INT64 || + mf.mtype == metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION || + mf.mtype == metricspb.MetricDescriptor_SUMMARY +} + +func (mf *metricFamily) getGroupKey(ls labels.Labels) string { + mf.updateLabelKeys(ls) + return dpgSignature(mf.labelKeysOrdered, ls) +} + +// getGroups to return groups in insertion order +func (mf *metricFamily) getGroups() []*metricGroup { + groups := make([]*metricGroup, len(mf.groupOrders)) + for k, v := range mf.groupOrders { + groups[v] = mf.groups[k] + } + + return groups +} + +func (mf *metricFamily) loadMetricGroupOrCreate(groupKey string, ls labels.Labels, ts int64) *metricGroup { + mg, ok := mf.groups[groupKey] + if !ok { + mg = &metricGroup{ + family: mf, + ts: ts, + ls: ls, + complexValue: make([]*dataPoint, 0), + } + mf.groups[groupKey] = mg + // maintaining data insertion order is helpful to generate stable/reproducible metric output + mf.groupOrders[groupKey] = len(mf.groupOrders) + } + return mg +} + +func (mf *metricFamily) getLabelKeys() []*metricspb.LabelKey { + lks := make([]*metricspb.LabelKey, len(mf.labelKeysOrdered)) + for i, k := range mf.labelKeysOrdered { + lks[i] = &metricspb.LabelKey{Key: k} + } + return lks +} + +func (mf *metricFamily) Add(metricName string, ls labels.Labels, t int64, v float64) error { + groupKey := mf.getGroupKey(ls) + mg := mf.loadMetricGroupOrCreate(groupKey, ls, t) + switch mf.mtype { + case metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION: + fallthrough + case metricspb.MetricDescriptor_SUMMARY: + switch { + case strings.HasSuffix(metricName, metricsSuffixSum): + // always use the timestamp from sum (count is ok too), because the startTs from quantiles won't be reliable + // in cases like remote server restart + mg.ts = t + mg.sum = v + mg.hasSum = true + case strings.HasSuffix(metricName, metricsSuffixCount): + mg.count = v + mg.hasCount = true + default: + boundary, err := getBoundary(mf.mtype, ls) + if err != nil { + mf.droppedTimeseries++ + return err + } + mg.complexValue = append(mg.complexValue, &dataPoint{value: v, boundary: boundary}) + } + default: + mg.value = v + } + + return nil +} + +func (mf *metricFamily) ToMetric() (*metricspb.Metric, int, int) { + timeseries := make([]*metricspb.TimeSeries, 0, len(mf.groups)) + switch mf.mtype { + // not supported currently + // case metricspb.MetricDescriptor_GAUGE_DISTRIBUTION: + // return nil + case metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION: + for _, mg := range mf.getGroups() { + tss := mg.toDistributionTimeSeries(mf.labelKeysOrdered) + if tss != nil { + timeseries = append(timeseries, tss) + } else { + mf.droppedTimeseries++ + } + } + case metricspb.MetricDescriptor_SUMMARY: + for _, mg := range mf.getGroups() { + tss := mg.toSummaryTimeSeries(mf.labelKeysOrdered) + if tss != nil { + timeseries = append(timeseries, tss) + } else { + mf.droppedTimeseries++ + } + } + default: + for _, mg := range mf.getGroups() { + tss := mg.toDoubleValueTimeSeries(mf.labelKeysOrdered) + if tss != nil { + timeseries = append(timeseries, tss) + } else { + mf.droppedTimeseries++ + } + } + } + + // note: the total number of timeseries is the length of timeseries plus the number of dropped timeseries. + numTimeseries := len(timeseries) + if numTimeseries != 0 { + return &metricspb.Metric{ + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: mf.name, + Description: mf.metadata.Help, + Unit: heuristicalMetricAndKnownUnits(mf.name, mf.metadata.Unit), + Type: mf.mtype, + LabelKeys: mf.getLabelKeys(), + }, + Timeseries: timeseries, + }, + numTimeseries + mf.droppedTimeseries, + mf.droppedTimeseries + } + return nil, mf.droppedTimeseries, mf.droppedTimeseries +} + +type dataPoint struct { + value float64 + boundary float64 +} + +// metricGroup, represents a single metric of a metric family. for example a histogram metric is usually represent by +// a couple data complexValue (buckets and count/sum), a group of a metric family always share a same set of tags. for +// simple types like counter and gauge, each data point is a group of itself +type metricGroup struct { + family *metricFamily + ts int64 + ls labels.Labels + count float64 + hasCount bool + sum float64 + hasSum bool + value float64 + complexValue []*dataPoint +} + +func (mg *metricGroup) sortPoints() { + sort.Slice(mg.complexValue, func(i, j int) bool { + return mg.complexValue[i].boundary < mg.complexValue[j].boundary + }) +} + +func (mg *metricGroup) toDistributionTimeSeries(orderedLabelKeys []string) *metricspb.TimeSeries { + if !(mg.hasCount && mg.hasSum) || len(mg.complexValue) == 0 { + return nil + } + mg.sortPoints() + // for OCAgent Proto, the bounds won't include +inf + bounds := make([]float64, len(mg.complexValue)-1) + buckets := make([]*metricspb.DistributionValue_Bucket, len(mg.complexValue)) + + for i := 0; i < len(mg.complexValue); i++ { + if i != len(mg.complexValue)-1 { + // not need to add +inf as bound to oc proto + bounds[i] = mg.complexValue[i].boundary + } + adjustedCount := mg.complexValue[i].value + if i != 0 { + adjustedCount -= mg.complexValue[i-1].value + } + buckets[i] = &metricspb.DistributionValue_Bucket{Count: int64(adjustedCount)} + } + + dv := &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: bounds, + }, + }, + }, + Count: int64(mg.count), + Sum: mg.sum, + Buckets: buckets, + // SumOfSquaredDeviation: // there's no way to compute this value from prometheus data + } + + return &metricspb.TimeSeries{ + StartTimestamp: timestampFromMs(mg.ts), + LabelValues: populateLabelValues(orderedLabelKeys, mg.ls), + Points: []*metricspb.Point{ + { + Timestamp: timestampFromMs(mg.ts), + Value: &metricspb.Point_DistributionValue{DistributionValue: dv}, + }, + }, + } +} + +func (mg *metricGroup) toSummaryTimeSeries(orderedLabelKeys []string) *metricspb.TimeSeries { + // expecting count and sum to be provided, however, in the following two cases, they can be missed. + // 1. data is corrupted + // 2. ignored by startValue evaluation + if !(mg.hasCount && mg.hasSum) { + return nil + } + mg.sortPoints() + percentiles := make([]*metricspb.SummaryValue_Snapshot_ValueAtPercentile, len(mg.complexValue)) + for i, p := range mg.complexValue { + percentiles[i] = + &metricspb.SummaryValue_Snapshot_ValueAtPercentile{Percentile: p.boundary * 100, Value: p.value} + } + + // allow percentiles to be nil when no data provided from prometheus + var snapshot *metricspb.SummaryValue_Snapshot + if len(percentiles) != 0 { + snapshot = &metricspb.SummaryValue_Snapshot{ + PercentileValues: percentiles, + } + } + + // Based on the summary description from https://prometheus.io/docs/concepts/metric_types/#summary + // the quantiles are calculated over a sliding time window, however, the count is the total count of + // observations and the corresponding sum is a sum of all observed values, thus the sum and count used + // at the global level of the metricspb.SummaryValue + + summaryValue := &metricspb.SummaryValue{ + Sum: &wrapperspb.DoubleValue{Value: mg.sum}, + Count: &wrapperspb.Int64Value{Value: int64(mg.count)}, + Snapshot: snapshot, + } + return &metricspb.TimeSeries{ + StartTimestamp: timestampFromMs(mg.ts), + LabelValues: populateLabelValues(orderedLabelKeys, mg.ls), + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(mg.ts), Value: &metricspb.Point_SummaryValue{SummaryValue: summaryValue}}, + }, + } +} + +func (mg *metricGroup) toDoubleValueTimeSeries(orderedLabelKeys []string) *metricspb.TimeSeries { + var startTs *timestamppb.Timestamp + // gauge/undefined types has no start time + if mg.family.isCumulativeType() { + startTs = timestampFromMs(mg.ts) + } + + return &metricspb.TimeSeries{ + StartTimestamp: startTs, + Points: []*metricspb.Point{{Timestamp: timestampFromMs(mg.ts), Value: &metricspb.Point_DoubleValue{DoubleValue: mg.value}}}, + LabelValues: populateLabelValues(orderedLabelKeys, mg.ls), + } +} + +func populateLabelValues(orderedKeys []string, ls labels.Labels) []*metricspb.LabelValue { + lvs := make([]*metricspb.LabelValue, len(orderedKeys)) + lmap := ls.Map() + for i, k := range orderedKeys { + value := lmap[k] + lvs[i] = &metricspb.LabelValue{Value: value, HasValue: value != ""} + } + return lvs +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster.go b/internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster.go new file mode 100644 index 00000000000..4e734faff3f --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster.go @@ -0,0 +1,371 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "fmt" + "strings" + "sync" + "time" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// Notes on garbage collection (gc): +// +// Job-level gc: +// The Prometheus receiver will likely execute in a long running service whose lifetime may exceed +// the lifetimes of many of the jobs that it is collecting from. In order to keep the JobsMap from +// leaking memory for entries of no-longer existing jobs, the JobsMap needs to remove entries that +// haven't been accessed for a long period of time. +// +// Timeseries-level gc: +// Some jobs that the Prometheus receiver is collecting from may export timeseries based on metrics +// from other jobs (e.g. cAdvisor). In order to keep the timeseriesMap from leaking memory for entries +// of no-longer existing jobs, the timeseriesMap for each job needs to remove entries that haven't +// been accessed for a long period of time. +// +// The gc strategy uses a standard mark-and-sweep approach - each time a timeseriesMap is accessed, +// it is marked. Similarly, each time a timeseriesinfo is accessed, it is also marked. +// +// At the end of each JobsMap.get(), if the last time the JobsMap was gc'd exceeds the 'gcInterval', +// the JobsMap is locked and any timeseriesMaps that are unmarked are removed from the JobsMap +// otherwise the timeseriesMap is gc'd +// +// The gc for the timeseriesMap is straightforward - the map is locked and, for each timeseriesinfo +// in the map, if it has not been marked, it is removed otherwise it is unmarked. +// +// Alternative Strategies +// 1. If the job-level gc doesn't run often enough, or runs too often, a separate go routine can +// be spawned at JobMap creation time that gc's at periodic intervals. This approach potentially +// adds more contention and latency to each scrape so the current approach is used. Note that +// the go routine will need to be cancelled upon Shutdown(). +// 2. If the gc of each timeseriesMap during the gc of the JobsMap causes too much contention, +// the gc of timeseriesMaps can be moved to the end of MetricsAdjuster().AdjustMetrics(). This +// approach requires adding 'lastGC' Time and (potentially) a gcInterval duration to +// timeseriesMap so the current approach is used instead. + +// timeseriesinfo contains the information necessary to adjust from the initial point and to detect +// resets. +type timeseriesinfo struct { + mark bool + initial *metricspb.TimeSeries + previous *metricspb.TimeSeries +} + +// timeseriesMap maps from a timeseries instance (metric * label values) to the timeseries info for +// the instance. +type timeseriesMap struct { + sync.RWMutex + mark bool + tsiMap map[string]*timeseriesinfo +} + +// Get the timeseriesinfo for the timeseries associated with the metric and label values. +func (tsm *timeseriesMap) get( + metric *metricspb.Metric, values []*metricspb.LabelValue) *timeseriesinfo { + name := metric.GetMetricDescriptor().GetName() + sig := getTimeseriesSignature(name, values) + tsi, ok := tsm.tsiMap[sig] + if !ok { + tsi = ×eriesinfo{} + tsm.tsiMap[sig] = tsi + } + tsm.mark = true + tsi.mark = true + return tsi +} + +// Remove timeseries that have aged out. +func (tsm *timeseriesMap) gc() { + tsm.Lock() + defer tsm.Unlock() + // this shouldn't happen under the current gc() strategy + if !tsm.mark { + return + } + for ts, tsi := range tsm.tsiMap { + if !tsi.mark { + delete(tsm.tsiMap, ts) + } else { + tsi.mark = false + } + } + tsm.mark = false +} + +func newTimeseriesMap() *timeseriesMap { + return ×eriesMap{mark: true, tsiMap: map[string]*timeseriesinfo{}} +} + +// Create a unique timeseries signature consisting of the metric name and label values. +func getTimeseriesSignature(name string, values []*metricspb.LabelValue) string { + labelValues := make([]string, 0, len(values)) + for _, label := range values { + if label.GetValue() != "" { + labelValues = append(labelValues, label.GetValue()) + } + } + return fmt.Sprintf("%s,%s", name, strings.Join(labelValues, ",")) +} + +// JobsMap maps from a job instance to a map of timeseries instances for the job. +type JobsMap struct { + sync.RWMutex + gcInterval time.Duration + lastGC time.Time + jobsMap map[string]*timeseriesMap +} + +// NewJobsMap creates a new (empty) JobsMap. +func NewJobsMap(gcInterval time.Duration) *JobsMap { + return &JobsMap{gcInterval: gcInterval, lastGC: time.Now(), jobsMap: make(map[string]*timeseriesMap)} +} + +// Remove jobs and timeseries that have aged out. +func (jm *JobsMap) gc() { + jm.Lock() + defer jm.Unlock() + // once the structure is locked, confirm that gc() is still necessary + if time.Since(jm.lastGC) > jm.gcInterval { + for sig, tsm := range jm.jobsMap { + tsm.RLock() + tsmNotMarked := !tsm.mark + tsm.RUnlock() + if tsmNotMarked { + delete(jm.jobsMap, sig) + } else { + tsm.gc() + } + } + jm.lastGC = time.Now() + } +} + +func (jm *JobsMap) maybeGC() { + // speculatively check if gc() is necessary, recheck once the structure is locked + jm.RLock() + defer jm.RUnlock() + if time.Since(jm.lastGC) > jm.gcInterval { + go jm.gc() + } +} + +func (jm *JobsMap) get(job, instance string) *timeseriesMap { + sig := job + ":" + instance + jm.RLock() + tsm, ok := jm.jobsMap[sig] + jm.RUnlock() + defer jm.maybeGC() + if ok { + return tsm + } + jm.Lock() + defer jm.Unlock() + tsm2, ok2 := jm.jobsMap[sig] + if ok2 { + return tsm2 + } + tsm2 = newTimeseriesMap() + jm.jobsMap[sig] = tsm2 + return tsm2 +} + +// MetricsAdjuster takes a map from a metric instance to the initial point in the metrics instance +// and provides AdjustMetrics, which takes a sequence of metrics and adjust their values based on +// the initial points. +type MetricsAdjuster struct { + tsm *timeseriesMap + logger *zap.Logger +} + +// NewMetricsAdjuster is a constructor for MetricsAdjuster. +func NewMetricsAdjuster(tsm *timeseriesMap, logger *zap.Logger) *MetricsAdjuster { + return &MetricsAdjuster{ + tsm: tsm, + logger: logger, + } +} + +// AdjustMetrics takes a sequence of metrics and adjust their values based on the initial and +// previous points in the timeseriesMap. If the metric is the first point in the timeseries, or the +// timeseries has been reset, it is removed from the sequence and added to the timeseriesMap. +// Additionally returns the total number of timeseries dropped from the metrics. +func (ma *MetricsAdjuster) AdjustMetrics(metrics []*metricspb.Metric) ([]*metricspb.Metric, int) { + var adjusted = make([]*metricspb.Metric, 0, len(metrics)) + dropped := 0 + ma.tsm.Lock() + defer ma.tsm.Unlock() + for _, metric := range metrics { + adj, d := ma.adjustMetric(metric) + dropped += d + if adj { + adjusted = append(adjusted, metric) + } + } + return adjusted, dropped +} + +// Returns true if at least one of the metric's timeseries was adjusted and false if all of the +// timeseries are an initial occurrence or a reset. Additionally returns the number of timeseries +// dropped from the metric. +// +// Types of metrics returned supported by prometheus: +// - MetricDescriptor_GAUGE_DOUBLE +// - MetricDescriptor_GAUGE_DISTRIBUTION +// - MetricDescriptor_CUMULATIVE_DOUBLE +// - MetricDescriptor_CUMULATIVE_DISTRIBUTION +// - MetricDescriptor_SUMMARY +func (ma *MetricsAdjuster) adjustMetric(metric *metricspb.Metric) (bool, int) { + switch metric.MetricDescriptor.Type { + case metricspb.MetricDescriptor_GAUGE_DOUBLE, metricspb.MetricDescriptor_GAUGE_DISTRIBUTION: + // gauges don't need to be adjusted so no additional processing is necessary + return true, 0 + default: + return ma.adjustMetricTimeseries(metric) + } +} + +// Returns true if at least one of the metric's timeseries was adjusted and false if all of the +// timeseries are an initial occurrence or a reset. Additionally returns the number of timeseries +// dropped. +func (ma *MetricsAdjuster) adjustMetricTimeseries(metric *metricspb.Metric) (bool, int) { + dropped := 0 + filtered := make([]*metricspb.TimeSeries, 0, len(metric.GetTimeseries())) + for _, current := range metric.GetTimeseries() { + tsi := ma.tsm.get(metric, current.GetLabelValues()) + if tsi.initial == nil { + // initial timeseries + tsi.initial = current + tsi.previous = current + dropped++ + } else { + if ma.adjustTimeseries(metric.MetricDescriptor.Type, current, tsi.initial, + tsi.previous) { + tsi.previous = current + filtered = append(filtered, current) + } else { + // reset timeseries + tsi.initial = current + tsi.previous = current + dropped++ + } + } + } + metric.Timeseries = filtered + return len(filtered) > 0, dropped +} + +// Returns true if 'current' was adjusted and false if 'current' is an the initial occurrence or a +// reset of the timeseries. +func (ma *MetricsAdjuster) adjustTimeseries(metricType metricspb.MetricDescriptor_Type, + current, initial, previous *metricspb.TimeSeries) bool { + if !ma.adjustPoints( + metricType, current.GetPoints(), initial.GetPoints(), previous.GetPoints()) { + return false + } + current.StartTimestamp = initial.StartTimestamp + return true +} + +func (ma *MetricsAdjuster) adjustPoints(metricType metricspb.MetricDescriptor_Type, + current, initial, previous []*metricspb.Point) bool { + if len(current) != 1 || len(initial) != 1 || len(current) != 1 { + ma.logger.Info("Adjusting Points, all lengths should be 1", + zap.Int("len(current)", len(current)), zap.Int("len(initial)", len(initial)), zap.Int("len(previous)", len(previous))) + return true + } + return ma.adjustPoint(metricType, current[0], initial[0], previous[0]) +} + +// Note: There is an important, subtle point here. When a new timeseries or a reset is detected, +// current and initial are the same object. When initial == previous, the previous value/count/sum +// are all the initial value. When initial != previous, the previous value/count/sum has been +// adjusted wrt the initial value so both they must be combined to find the actual previous +// value/count/sum. This happens because the timeseries are updated in-place - if new copies of the +// timeseries were created instead, previous could be used directly but this would mean reallocating +// all of the metrics. +func (ma *MetricsAdjuster) adjustPoint(metricType metricspb.MetricDescriptor_Type, + current, initial, previous *metricspb.Point) bool { + switch metricType { + case metricspb.MetricDescriptor_CUMULATIVE_DOUBLE: + currentValue := current.GetDoubleValue() + initialValue := initial.GetDoubleValue() + previousValue := initialValue + if initial != previous { + previousValue += previous.GetDoubleValue() + } + if currentValue < previousValue { + // reset detected + return false + } + current.Value = + &metricspb.Point_DoubleValue{DoubleValue: currentValue - initialValue} + case metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION: + // note: sum of squared deviation not currently supported + currentDist := current.GetDistributionValue() + initialDist := initial.GetDistributionValue() + previousCount := initialDist.Count + previousSum := initialDist.Sum + if initial != previous { + previousCount += previous.GetDistributionValue().Count + previousSum += previous.GetDistributionValue().Sum + } + if currentDist.Count < previousCount || currentDist.Sum < previousSum { + // reset detected + return false + } + currentDist.Count -= initialDist.Count + currentDist.Sum -= initialDist.Sum + ma.adjustBuckets(currentDist.Buckets, initialDist.Buckets) + case metricspb.MetricDescriptor_SUMMARY: + // note: for summary, we don't adjust the snapshot + currentCount := current.GetSummaryValue().Count.GetValue() + currentSum := current.GetSummaryValue().Sum.GetValue() + initialCount := initial.GetSummaryValue().Count.GetValue() + initialSum := initial.GetSummaryValue().Sum.GetValue() + previousCount := initialCount + previousSum := initialSum + if initial != previous { + previousCount += previous.GetSummaryValue().Count.GetValue() + previousSum += previous.GetSummaryValue().Sum.GetValue() + } + if currentCount < previousCount || currentSum < previousSum { + // reset detected + return false + } + current.GetSummaryValue().Count = + &wrapperspb.Int64Value{Value: currentCount - initialCount} + current.GetSummaryValue().Sum = + &wrapperspb.DoubleValue{Value: currentSum - initialSum} + default: + // this shouldn't happen + ma.logger.Info("Adjust - skipping unexpected point", zap.String("type", metricType.String())) + } + return true +} + +func (ma *MetricsAdjuster) adjustBuckets(current, initial []*metricspb.DistributionValue_Bucket) { + if len(current) != len(initial) { + // this shouldn't happen + ma.logger.Info("Bucket sizes not equal", zap.Int("len(current)", len(current)), zap.Int("len(initial)", len(initial))) + return + } + for i := 0; i < len(current); i++ { + current[i].Count -= initial[i].Count + } +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster_test.go b/internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster_test.go new file mode 100644 index 00000000000..c2236a4398d --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/metrics_adjuster_test.go @@ -0,0 +1,377 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + "time" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + mtu "go.opentelemetry.io/collector/testutil/metricstestutil" +) + +func Test_gauge(t *testing.T) { + script := []*metricsAdjusterTest{{ + "Gauge: round 1 - gauge not adjusted", + []*metricspb.Metric{mtu.Gauge(g1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44)))}, + []*metricspb.Metric{mtu.Gauge(g1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44)))}, + }, { + "Gauge: round 2 - gauge not adjusted", + []*metricspb.Metric{mtu.Gauge(g1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66)))}, + []*metricspb.Metric{mtu.Gauge(g1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66)))}, + }, { + "Gauge: round 3 - value less than previous value - gauge is not adjusted", + []*metricspb.Metric{mtu.Gauge(g1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 55)))}, + []*metricspb.Metric{mtu.Gauge(g1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 55)))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_gaugeDistribution(t *testing.T) { + script := []*metricsAdjusterTest{{ + "GaugeDist: round 1 - gauge distribution not adjusted", + []*metricspb.Metric{mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7})))}, + []*metricspb.Metric{mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7})))}, + }, { + "GaugeDist: round 2 - gauge distribution not adjusted", + []*metricspb.Metric{mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{6, 5, 8, 11})))}, + []*metricspb.Metric{mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{6, 5, 8, 11})))}, + }, { + "GaugeDist: round 3 - count/sum less than previous - gauge distribution not adjusted", + []*metricspb.Metric{mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{2, 0, 1, 5})))}, + []*metricspb.Metric{mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{2, 0, 1, 5})))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_cumulative(t *testing.T) { + script := []*metricsAdjusterTest{{ + "Cumulative: round 1 - initial instance, adjusted should be empty", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44)))}, + []*metricspb.Metric{}, + }, { + "Cumulative: round 2 - instance adjusted based on round 1", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66)))}, + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t2Ms, 22)))}, + }, { + "Cumulative: round 3 - instance reset (value less than previous value), adjusted should be empty", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 55)))}, + []*metricspb.Metric{}, + }, { + "Cumulative: round 4 - instance adjusted based on round 3", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.Double(t4Ms, 72)))}, + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t4Ms, 17)))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_cumulativeDistribution(t *testing.T) { + script := []*metricsAdjusterTest{{ + "CumulativeDist: round 1 - initial instance, adjusted should be empty", + []*metricspb.Metric{mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7})))}, + []*metricspb.Metric{}, + }, { + "CumulativeDist: round 2 - instance adjusted based on round 1", + []*metricspb.Metric{mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{6, 3, 4, 8})))}, + []*metricspb.Metric{mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{2, 1, 1, 1})))}, + }, { + "CumulativeDist: round 3 - instance reset (value less than previous value), adjusted should be empty", + []*metricspb.Metric{mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{5, 3, 2, 7})))}, + []*metricspb.Metric{}, + }, { + "CumulativeDist: round 4 - instance adjusted based on round 3", + []*metricspb.Metric{mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.DistPt(t4Ms, bounds0, []int64{7, 4, 2, 12})))}, + []*metricspb.Metric{mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t4Ms, bounds0, []int64{2, 1, 0, 5})))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_summary(t *testing.T) { + script := []*metricsAdjusterTest{{ + "Summary: round 1 - initial instance, adjusted should be empty", + []*metricspb.Metric{mtu.Summary(s1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.SummPt(t1Ms, 10, 40, percent0, []float64{1, 5, 8})))}, + []*metricspb.Metric{}, + }, { + "Summary: round 2 - instance adjusted based on round 1", + []*metricspb.Metric{mtu.Summary(s1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.SummPt(t2Ms, 15, 70, percent0, []float64{7, 44, 9})))}, + []*metricspb.Metric{mtu.Summary(s1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.SummPt(t2Ms, 5, 30, percent0, []float64{7, 44, 9})))}, + }, { + "Summary: round 3 - instance reset (count less than previous), adjusted should be empty", + []*metricspb.Metric{mtu.Summary(s1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.SummPt(t3Ms, 12, 66, percent0, []float64{3, 22, 5})))}, + []*metricspb.Metric{}, + }, { + "Summary: round 4 - instance adjusted based on round 3", + []*metricspb.Metric{mtu.Summary(s1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.SummPt(t4Ms, 14, 96, percent0, []float64{9, 47, 8})))}, + []*metricspb.Metric{mtu.Summary(s1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.SummPt(t4Ms, 2, 30, percent0, []float64{9, 47, 8})))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_multiMetrics(t *testing.T) { + script := []*metricsAdjusterTest{{ + "MultiMetrics: round 1 - combined round 1 of individual metrics", + []*metricspb.Metric{ + mtu.Gauge(g1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44))), + mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7}))), + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7}))), + mtu.Summary(s1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.SummPt(t1Ms, 10, 40, percent0, []float64{1, 5, 8}))), + }, + []*metricspb.Metric{ + mtu.Gauge(g1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44))), + mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7}))), + }, + }, { + "MultiMetrics: round 2 - combined round 2 of individual metrics", + []*metricspb.Metric{ + mtu.Gauge(g1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66))), + mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{6, 5, 8, 11}))), + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{6, 3, 4, 8}))), + mtu.Summary(s1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.SummPt(t2Ms, 15, 70, percent0, []float64{7, 44, 9}))), + }, + []*metricspb.Metric{ + mtu.Gauge(g1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66))), + mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{6, 5, 8, 11}))), + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t2Ms, 22))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{2, 1, 1, 1}))), + mtu.Summary(s1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.SummPt(t2Ms, 5, 30, percent0, []float64{7, 44, 9}))), + }, + }, { + "MultiMetrics: round 3 - combined round 3 of individual metrics", + []*metricspb.Metric{ + mtu.Gauge(g1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 55))), + mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{2, 0, 1, 5}))), + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 55))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{5, 3, 2, 7}))), + mtu.Summary(s1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.SummPt(t3Ms, 12, 66, percent0, []float64{3, 22, 5}))), + }, + []*metricspb.Metric{ + mtu.Gauge(g1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 55))), + mtu.GaugeDist(gd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{2, 0, 1, 5}))), + }, + }, { + "MultiMetrics: round 4 - combined round 4 of individual metrics", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.Double(t4Ms, 72))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.DistPt(t4Ms, bounds0, []int64{7, 4, 2, 12}))), + mtu.Summary(s1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.SummPt(t4Ms, 14, 96, percent0, []float64{9, 47, 8}))), + }, + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t4Ms, 17))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t4Ms, bounds0, []int64{2, 1, 0, 5}))), + mtu.Summary(s1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.SummPt(t4Ms, 2, 30, percent0, []float64{9, 47, 8}))), + }, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_multiTimeseries(t *testing.T) { + script := []*metricsAdjusterTest{{ + "MultiTimeseries: round 1 - initial first instance, adjusted should be empty", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44)))}, + []*metricspb.Metric{}, + }, { + "MultiTimeseries: round 2 - first instance adjusted based on round 1, initial second instance", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 66)), mtu.Timeseries(t2Ms, v10v20, mtu.Double(t2Ms, 20)))}, + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t2Ms, 22)))}, + }, { + "MultiTimeseries: round 3 - first instance adjusted based on round 1, second based on round 2", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 88)), mtu.Timeseries(t3Ms, v10v20, mtu.Double(t3Ms, 49)))}, + []*metricspb.Metric{mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t3Ms, 44)), mtu.Timeseries(t2Ms, v10v20, mtu.Double(t3Ms, 29)))}, + }, { + "MultiTimeseries: round 4 - first instance reset, second instance adjusted based on round 2, initial third instance", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.Double(t4Ms, 87)), mtu.Timeseries(t4Ms, v10v20, mtu.Double(t4Ms, 57)), mtu.Timeseries(t4Ms, v100v200, mtu.Double(t4Ms, 10)))}, + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t2Ms, v10v20, mtu.Double(t4Ms, 37)))}, + }, { + "MultiTimeseries: round 5 - first instance adusted based on round 4, second on round 2, third on round 4", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t5Ms, v1v2, mtu.Double(t5Ms, 90)), mtu.Timeseries(t5Ms, v10v20, mtu.Double(t5Ms, 65)), mtu.Timeseries(t5Ms, v100v200, mtu.Double(t5Ms, 22)))}, + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.Double(t5Ms, 3)), mtu.Timeseries(t2Ms, v10v20, mtu.Double(t5Ms, 45)), mtu.Timeseries(t4Ms, v100v200, mtu.Double(t5Ms, 12)))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_emptyLabels(t *testing.T) { + script := []*metricsAdjusterTest{{ + "EmptyLabels: round 1 - initial instance, implicitly empty labels, adjusted should be empty", + []*metricspb.Metric{mtu.Cumulative(c1, []string{}, mtu.Timeseries(t1Ms, []string{}, mtu.Double(t1Ms, 44)))}, + []*metricspb.Metric{}, + }, { + "EmptyLabels: round 2 - instance adjusted based on round 1", + []*metricspb.Metric{mtu.Cumulative(c1, []string{}, mtu.Timeseries(t2Ms, []string{}, mtu.Double(t2Ms, 66)))}, + []*metricspb.Metric{mtu.Cumulative(c1, []string{}, mtu.Timeseries(t1Ms, []string{}, mtu.Double(t2Ms, 22)))}, + }, { + "EmptyLabels: round 3 - one explicitly empty label, instance adjusted based on round 1", + []*metricspb.Metric{mtu.Cumulative(c1, k1, mtu.Timeseries(t3Ms, []string{""}, mtu.Double(t3Ms, 77)))}, + []*metricspb.Metric{mtu.Cumulative(c1, k1, mtu.Timeseries(t1Ms, []string{""}, mtu.Double(t3Ms, 33)))}, + }, { + "EmptyLabels: round 4 - three explicitly empty labels, instance adjusted based on round 1", + []*metricspb.Metric{mtu.Cumulative(c1, k1k2k3, mtu.Timeseries(t3Ms, []string{"", "", ""}, mtu.Double(t3Ms, 88)))}, + []*metricspb.Metric{mtu.Cumulative(c1, k1k2k3, mtu.Timeseries(t1Ms, []string{"", "", ""}, mtu.Double(t3Ms, 44)))}, + }} + runScript(t, NewJobsMap(time.Minute).get("job", "0"), script) +} + +func Test_tsGC(t *testing.T) { + script1 := []*metricsAdjusterTest{{ + "TsGC: round 1 - initial instances, adjusted should be empty", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44)), mtu.Timeseries(t1Ms, v10v20, mtu.Double(t1Ms, 20))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7})), mtu.Timeseries(t1Ms, v10v20, mtu.DistPt(t1Ms, bounds0, []int64{40, 20, 30, 70}))), + }, + []*metricspb.Metric{}, + }} + + script2 := []*metricsAdjusterTest{{ + "TsGC: round 2 - metrics first timeseries adjusted based on round 2, second timeseries not updated", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.Double(t2Ms, 88))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t2Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{8, 7, 9, 14}))), + }, + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t2Ms, 44))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t2Ms, bounds0, []int64{4, 5, 6, 7}))), + }, + }} + + script3 := []*metricsAdjusterTest{{ + "TsGC: round 3 - metrics first timeseries adjusted based on round 2, second timeseries empty due to timeseries gc()", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.Double(t3Ms, 99)), mtu.Timeseries(t3Ms, v10v20, mtu.Double(t3Ms, 80))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t3Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{9, 8, 10, 15})), mtu.Timeseries(t3Ms, v10v20, mtu.DistPt(t3Ms, bounds0, []int64{55, 66, 33, 77}))), + }, + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t3Ms, 55))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t3Ms, bounds0, []int64{5, 6, 7, 8}))), + }, + }} + + jobsMap := NewJobsMap(time.Minute) + + // run round 1 + runScript(t, jobsMap.get("job", "0"), script1) + // gc the tsmap, unmarking all entries + jobsMap.get("job", "0").gc() + // run round 2 - update metrics first timeseries only + runScript(t, jobsMap.get("job", "0"), script2) + // gc the tsmap, collecting umarked entries + jobsMap.get("job", "0").gc() + // run round 3 - verify that metrics second timeseries have been gc'd + runScript(t, jobsMap.get("job", "0"), script3) +} + +func Test_jobGC(t *testing.T) { + job1Script1 := []*metricsAdjusterTest{{ + "JobGC: job 1, round 1 - initial instances, adjusted should be empty", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.Double(t1Ms, 44)), mtu.Timeseries(t1Ms, v10v20, mtu.Double(t1Ms, 20))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t1Ms, v1v2, mtu.DistPt(t1Ms, bounds0, []int64{4, 2, 3, 7})), mtu.Timeseries(t1Ms, v10v20, mtu.DistPt(t1Ms, bounds0, []int64{40, 20, 30, 70}))), + }, + []*metricspb.Metric{}, + }} + + job2Script1 := []*metricsAdjusterTest{{ + "JobGC: job2, round 1 - no metrics adjusted, just trigger gc", + []*metricspb.Metric{}, + []*metricspb.Metric{}, + }} + + job1Script2 := []*metricsAdjusterTest{{ + "JobGC: job 1, round 2 - metrics timeseries empty due to job-level gc", + []*metricspb.Metric{ + mtu.Cumulative(c1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.Double(t4Ms, 99)), mtu.Timeseries(t4Ms, v10v20, mtu.Double(t4Ms, 80))), + mtu.CumulativeDist(cd1, k1k2, mtu.Timeseries(t4Ms, v1v2, mtu.DistPt(t4Ms, bounds0, []int64{9, 8, 10, 15})), mtu.Timeseries(t4Ms, v10v20, mtu.DistPt(t4Ms, bounds0, []int64{55, 66, 33, 77}))), + }, + []*metricspb.Metric{}, + }} + + gcInterval := 10 * time.Millisecond + jobsMap := NewJobsMap(gcInterval) + + // run job 1, round 1 - all entries marked + runScript(t, jobsMap.get("job", "0"), job1Script1) + // sleep longer than gcInterval to enable job gc in the next run + time.Sleep(2 * gcInterval) + // run job 2, round1 - trigger job gc, unmarking all entries + runScript(t, jobsMap.get("job", "1"), job2Script1) + // sleep longer than gcInterval to enable job gc in the next run + time.Sleep(2 * gcInterval) + // re-run job 2, round1 - trigger job gc, removing unmarked entries + runScript(t, jobsMap.get("job", "1"), job2Script1) + // ensure that at least one jobsMap.gc() completed + jobsMap.gc() + // run job 1, round 2 - verify that all job 1 timeseries have been gc'd + runScript(t, jobsMap.get("job", "0"), job1Script2) +} + +var ( + g1 = "gauge1" + gd1 = "gaugedist1" + c1 = "cumulative1" + cd1 = "cumulativedist1" + s1 = "summary1" + k1 = []string{"k1"} + k1k2 = []string{"k1", "k2"} + k1k2k3 = []string{"k1", "k2", "k3"} + v1v2 = []string{"v1", "v2"} + v10v20 = []string{"v10", "v20"} + v100v200 = []string{"v100", "v200"} + bounds0 = []float64{1, 2, 4} + percent0 = []float64{10, 50, 90} + t1Ms = time.Unix(0, 1000000) + t2Ms = time.Unix(0, 2000000) + t3Ms = time.Unix(0, 3000000) + t4Ms = time.Unix(0, 5000000) + t5Ms = time.Unix(0, 5000000) +) + +type metricsAdjusterTest struct { + description string + metrics []*metricspb.Metric + adjusted []*metricspb.Metric +} + +func (mat *metricsAdjusterTest) dropped() int { + metricsTimeseries := 0 + for _, metric := range mat.metrics { + metricsTimeseries += len(metric.GetTimeseries()) + } + + adjustedTimeseries := 0 + for _, adjusted := range mat.adjusted { + adjustedTimeseries += len(adjusted.GetTimeseries()) + } + return metricsTimeseries - adjustedTimeseries +} + +func runScript(t *testing.T, tsm *timeseriesMap, script []*metricsAdjusterTest) { + l := zap.NewNop() + defer l.Sync() // flushes buffer, if any + ma := NewMetricsAdjuster(tsm, l) + + for _, test := range script { + expectedDropped := test.dropped() + adjusted, dropped := ma.AdjustMetrics(test.metrics) + assert.EqualValuesf(t, test.adjusted, adjusted, "Test: %v - expected: %v, actual: %v", test.description, test.adjusted, adjusted) + assert.Equalf(t, expectedDropped, dropped, "Test: %v", test.description) + } +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder.go b/internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder.go new file mode 100644 index 00000000000..fd18f8f900c --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder.go @@ -0,0 +1,303 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "errors" + "fmt" + "regexp" + "strconv" + "strings" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/pkg/textparse" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + metricsSuffixCount = "_count" + metricsSuffixBucket = "_bucket" + metricsSuffixSum = "_sum" + startTimeMetricName = "process_start_time_seconds" + scrapeUpMetricName = "up" +) + +var ( + trimmableSuffixes = []string{metricsSuffixBucket, metricsSuffixCount, metricsSuffixSum} + errNoDataToBuild = errors.New("there's no data to build") + errNoBoundaryLabel = errors.New("given metricType has no BucketLabel or QuantileLabel") + errEmptyBoundaryLabel = errors.New("BucketLabel or QuantileLabel is empty") +) + +type metricBuilder struct { + hasData bool + hasInternalMetric bool + mc MetadataCache + metrics []*metricspb.Metric + numTimeseries int + droppedTimeseries int + useStartTimeMetric bool + startTimeMetricRegex *regexp.Regexp + startTime float64 + logger *zap.Logger + currentMf MetricFamily +} + +// newMetricBuilder creates a MetricBuilder which is allowed to feed all the datapoints from a single prometheus +// scraped page by calling its AddDataPoint function, and turn them into an opencensus data.MetricsData object +// by calling its Build function +func newMetricBuilder(mc MetadataCache, useStartTimeMetric bool, startTimeMetricRegex string, logger *zap.Logger) *metricBuilder { + var regex *regexp.Regexp + if startTimeMetricRegex != "" { + regex, _ = regexp.Compile(startTimeMetricRegex) + } + return &metricBuilder{ + mc: mc, + metrics: make([]*metricspb.Metric, 0), + logger: logger, + numTimeseries: 0, + droppedTimeseries: 0, + useStartTimeMetric: useStartTimeMetric, + startTimeMetricRegex: regex, + } +} + +func (b *metricBuilder) matchStartTimeMetric(metricName string) bool { + if b.startTimeMetricRegex != nil { + return b.startTimeMetricRegex.MatchString(metricName) + } + + return metricName == startTimeMetricName +} + +// AddDataPoint is for feeding prometheus data complexValue in its processing order +func (b *metricBuilder) AddDataPoint(ls labels.Labels, t int64, v float64) error { + metricName := ls.Get(model.MetricNameLabel) + switch { + case metricName == "": + b.numTimeseries++ + b.droppedTimeseries++ + return errMetricNameNotFound + case isInternalMetric(metricName): + b.hasInternalMetric = true + lm := ls.Map() + delete(lm, model.MetricNameLabel) + // See https://www.prometheus.io/docs/concepts/jobs_instances/#automatically-generated-labels-and-time-series + // up: 1 if the instance is healthy, i.e. reachable, or 0 if the scrape failed. + if metricName == scrapeUpMetricName && v != 1.0 { + if v == 0.0 { + b.logger.Warn("Failed to scrape Prometheus endpoint", + zap.Int64("scrape_timestamp", t), + zap.String("target_labels", fmt.Sprintf("%v", lm))) + } else { + b.logger.Warn("The 'up' metric contains invalid value", + zap.Float64("value", v), + zap.Int64("scrape_timestamp", t), + zap.String("target_labels", fmt.Sprintf("%v", lm))) + } + } + return nil + case b.useStartTimeMetric && b.matchStartTimeMetric(metricName): + b.startTime = v + } + + b.hasData = true + + if b.currentMf != nil && !b.currentMf.IsSameFamily(metricName) { + m, ts, dts := b.currentMf.ToMetric() + b.numTimeseries += ts + b.droppedTimeseries += dts + if m != nil { + b.metrics = append(b.metrics, m) + } + b.currentMf = newMetricFamily(metricName, b.mc) + } else if b.currentMf == nil { + b.currentMf = newMetricFamily(metricName, b.mc) + } + + return b.currentMf.Add(metricName, ls, t, v) +} + +// Build an opencensus data.MetricsData based on all added data complexValue. +// The only error returned by this function is errNoDataToBuild. +func (b *metricBuilder) Build() ([]*metricspb.Metric, int, int, error) { + if !b.hasData { + if b.hasInternalMetric { + return make([]*metricspb.Metric, 0), 0, 0, nil + } + return nil, 0, 0, errNoDataToBuild + } + + if b.currentMf != nil { + m, ts, dts := b.currentMf.ToMetric() + b.numTimeseries += ts + b.droppedTimeseries += dts + if m != nil { + b.metrics = append(b.metrics, m) + } + b.currentMf = nil + } + + return b.metrics, b.numTimeseries, b.droppedTimeseries, nil +} + +// TODO: move the following helper functions to a proper place, as they are not called directly in this go file + +func isUsefulLabel(mType metricspb.MetricDescriptor_Type, labelKey string) bool { + result := false + switch labelKey { + case model.MetricNameLabel: + case model.InstanceLabel: + case model.SchemeLabel: + case model.MetricsPathLabel: + case model.JobLabel: + case model.BucketLabel: + result = mType != metricspb.MetricDescriptor_GAUGE_DISTRIBUTION && + mType != metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION + case model.QuantileLabel: + result = mType != metricspb.MetricDescriptor_SUMMARY + default: + result = true + } + return result +} + +// dpgSignature is used to create a key for data complexValue belong to a same group of a metric family +func dpgSignature(orderedKnownLabelKeys []string, ls labels.Labels) string { + sign := make([]string, 0, len(orderedKnownLabelKeys)) + for _, k := range orderedKnownLabelKeys { + v := ls.Get(k) + if v == "" { + continue + } + sign = append(sign, k+"="+v) + } + return fmt.Sprintf("%#v", sign) +} + +func normalizeMetricName(name string) string { + for _, s := range trimmableSuffixes { + if strings.HasSuffix(name, s) && name != s { + return strings.TrimSuffix(name, s) + } + } + return name +} + +func getBoundary(metricType metricspb.MetricDescriptor_Type, labels labels.Labels) (float64, error) { + labelName := "" + switch metricType { + case metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + metricspb.MetricDescriptor_GAUGE_DISTRIBUTION: + labelName = model.BucketLabel + case metricspb.MetricDescriptor_SUMMARY: + labelName = model.QuantileLabel + default: + return 0, errNoBoundaryLabel + } + + v := labels.Get(labelName) + if v == "" { + return 0, errEmptyBoundaryLabel + } + + return strconv.ParseFloat(v, 64) +} + +func convToOCAMetricType(metricType textparse.MetricType) metricspb.MetricDescriptor_Type { + switch metricType { + case textparse.MetricTypeCounter: + // always use float64, as it's the internal data type used in prometheus + return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE + // textparse.MetricTypeUnknown is converted to gauge by default to fix Prometheus untyped metrics from being dropped + case textparse.MetricTypeGauge, textparse.MetricTypeUnknown: + return metricspb.MetricDescriptor_GAUGE_DOUBLE + case textparse.MetricTypeHistogram: + return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION + // dropping support for gaugehistogram for now until we have an official spec of its implementation + // a draft can be found in: https://docs.google.com/document/d/1KwV0mAXwwbvvifBvDKH_LU1YjyXE_wxCkHNoCGq1GX0/edit#heading=h.1cvzqd4ksd23 + // case textparse.MetricTypeGaugeHistogram: + // return metricspb.MetricDescriptor_GAUGE_DISTRIBUTION + case textparse.MetricTypeSummary: + return metricspb.MetricDescriptor_SUMMARY + default: + // including: textparse.MetricTypeInfo, textparse.MetricTypeStateset + return metricspb.MetricDescriptor_UNSPECIFIED + } +} + +/* + code borrowed from the original promreceiver +*/ + +func heuristicalMetricAndKnownUnits(metricName, parsedUnit string) string { + if parsedUnit != "" { + return parsedUnit + } + lastUnderscoreIndex := strings.LastIndex(metricName, "_") + if lastUnderscoreIndex <= 0 || lastUnderscoreIndex >= len(metricName)-1 { + return "" + } + + unit := "" + + supposedUnit := metricName[lastUnderscoreIndex+1:] + switch strings.ToLower(supposedUnit) { + case "millisecond", "milliseconds", "ms": + unit = "ms" + case "second", "seconds", "s": + unit = "s" + case "microsecond", "microseconds", "us": + unit = "us" + case "nanosecond", "nanoseconds", "ns": + unit = "ns" + case "byte", "bytes", "by": + unit = "By" + case "bit", "bits": + unit = "Bi" + case "kilogram", "kilograms", "kg": + unit = "kg" + case "gram", "grams", "g": + unit = "g" + case "meter", "meters", "metre", "metres", "m": + unit = "m" + case "kilometer", "kilometers", "kilometre", "kilometres", "km": + unit = "km" + case "milimeter", "milimeters", "milimetre", "milimetres", "mm": + unit = "mm" + case "nanogram", "ng", "nanograms": + unit = "ng" + } + + return unit +} + +func timestampFromMs(timeAtMs int64) *timestamppb.Timestamp { + secs, ns := timeAtMs/1e3, (timeAtMs%1e3)*1e6 + return ×tamppb.Timestamp{ + Seconds: secs, + Nanos: int32(ns), + } +} + +func isInternalMetric(metricName string) bool { + if metricName == scrapeUpMetricName || strings.HasPrefix(metricName, "scrape_") { + return true + } + return false +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder_test.go b/internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder_test.go new file mode 100644 index 00000000000..7a64bbf1635 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/metricsbuilder_test.go @@ -0,0 +1,1380 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "reflect" + "testing" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/pkg/textparse" + "github.com/prometheus/prometheus/scrape" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const startTs = int64(1555366610000) +const interval = int64(15 * 1000) +const defaultBuilderStartTime = float64(1.0) + +var testMetadata = map[string]scrape.MetricMetadata{ + "counter_test": {Metric: "counter_test", Type: textparse.MetricTypeCounter, Help: "", Unit: ""}, + "counter_test2": {Metric: "counter_test2", Type: textparse.MetricTypeCounter, Help: "", Unit: ""}, + "gauge_test": {Metric: "gauge_test", Type: textparse.MetricTypeGauge, Help: "", Unit: ""}, + "gauge_test2": {Metric: "gauge_test2", Type: textparse.MetricTypeGauge, Help: "", Unit: ""}, + "hist_test": {Metric: "hist_test", Type: textparse.MetricTypeHistogram, Help: "", Unit: ""}, + "hist_test2": {Metric: "hist_test2", Type: textparse.MetricTypeHistogram, Help: "", Unit: ""}, + "ghist_test": {Metric: "ghist_test", Type: textparse.MetricTypeGaugeHistogram, Help: "", Unit: ""}, + "summary_test": {Metric: "summary_test", Type: textparse.MetricTypeSummary, Help: "", Unit: ""}, + "summary_test2": {Metric: "summary_test2", Type: textparse.MetricTypeSummary, Help: "", Unit: ""}, + "unknown_test": {Metric: "unknown_test", Type: textparse.MetricTypeUnknown, Help: "", Unit: ""}, + "poor_name_count": {Metric: "poor_name_count", Type: textparse.MetricTypeCounter, Help: "", Unit: ""}, + "up": {Metric: "up", Type: textparse.MetricTypeCounter, Help: "", Unit: ""}, + "scrape_foo": {Metric: "scrape_foo", Type: textparse.MetricTypeCounter, Help: "", Unit: ""}, + "example_process_start_time_seconds": {Metric: "example_process_start_time_seconds", + Type: textparse.MetricTypeGauge, Help: "", Unit: ""}, + "process_start_time_seconds": {Metric: "process_start_time_seconds", + Type: textparse.MetricTypeGauge, Help: "", Unit: ""}, + "badprocess_start_time_seconds": {Metric: "badprocess_start_time_seconds", + Type: textparse.MetricTypeGauge, Help: "", Unit: ""}, +} + +type testDataPoint struct { + lb labels.Labels + t int64 + v float64 +} + +type testScrapedPage struct { + pts []*testDataPoint +} + +type buildTestData struct { + name string + inputs []*testScrapedPage + wants [][]*metricspb.Metric +} + +func createLabels(mFamily string, tagPairs ...string) labels.Labels { + lm := make(map[string]string) + lm[model.MetricNameLabel] = mFamily + if len(tagPairs)%2 != 0 { + panic("tag pairs is not even") + } + + for i := 0; i < len(tagPairs); i += 2 { + lm[tagPairs[i]] = tagPairs[i+1] + } + + return labels.FromMap(lm) +} + +func createDataPoint(mname string, value float64, tagPairs ...string) *testDataPoint { + return &testDataPoint{ + lb: createLabels(mname, tagPairs...), + v: value, + } +} + +func runBuilderTests(t *testing.T, tests []buildTestData) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.EqualValues(t, len(tt.wants), len(tt.inputs)) + mc := newMockMetadataCache(testMetadata) + st := startTs + for i, page := range tt.inputs { + b := newMetricBuilder(mc, true, "", testLogger) + b.startTime = defaultBuilderStartTime // set to a non-zero value + for _, pt := range page.pts { + // set ts for testing + pt.t = st + assert.NoError(t, b.AddDataPoint(pt.lb, pt.t, pt.v)) + } + metrics, _, _, err := b.Build() + assert.NoError(t, err) + assert.EqualValues(t, tt.wants[i], metrics) + st += interval + } + }) + } +} + +func runBuilderStartTimeTests(t *testing.T, tests []buildTestData, + startTimeMetricRegex string, expectedBuilderStartTime float64) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mc := newMockMetadataCache(testMetadata) + st := startTs + for _, page := range tt.inputs { + b := newMetricBuilder(mc, true, startTimeMetricRegex, + testLogger) + b.startTime = defaultBuilderStartTime // set to a non-zero value + for _, pt := range page.pts { + // set ts for testing + pt.t = st + assert.NoError(t, b.AddDataPoint(pt.lb, pt.t, pt.v)) + } + _, _, _, err := b.Build() + assert.NoError(t, err) + assert.EqualValues(t, b.startTime, expectedBuilderStartTime) + st += interval + } + }) + } +} + +func Test_startTimeMetricMatch(t *testing.T) { + matchBuilderStartTime := 123.456 + matchTests := []buildTestData{ + { + name: "prefix_match", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("example_process_start_time_seconds", + matchBuilderStartTime, "foo", "bar"), + }, + }, + }, + }, + { + name: "match", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("process_start_time_seconds", + matchBuilderStartTime, "foo", "bar"), + }, + }, + }, + }, + } + nomatchTests := []buildTestData{ + { + name: "nomatch1", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("_process_start_time_seconds", + matchBuilderStartTime, "foo", "bar"), + }, + }, + }, + }, + { + name: "nomatch2", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("subprocess_start_time_seconds", + matchBuilderStartTime, "foo", "bar"), + }, + }, + }, + }, + } + + runBuilderStartTimeTests(t, matchTests, "^(.+_)*process_start_time_seconds$", matchBuilderStartTime) + runBuilderStartTimeTests(t, nomatchTests, "^(.+_)*process_start_time_seconds$", defaultBuilderStartTime) +} + +func Test_metricBuilder_counters(t *testing.T) { + tests := []buildTestData{ + { + name: "single-item", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("counter_test", 100, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "counter_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "two-items", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("counter_test", 150, "foo", "bar"), + createDataPoint("counter_test", 25, "foo", "other"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "counter_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 150.0}}, + }, + }, + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "other", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 25.0}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "two-metrics", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("counter_test", 150, "foo", "bar"), + createDataPoint("counter_test", 25, "foo", "other"), + createDataPoint("counter_test2", 100, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "counter_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 150.0}}, + }, + }, + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "other", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 25.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "counter_test2", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "metrics-with-poor-names", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("poor_name_count", 100, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "poor_name_count", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + }, + }, + }, + } + + runBuilderTests(t, tests) +} + +func Test_metricBuilder_gauges(t *testing.T) { + tests := []buildTestData{ + { + name: "one-gauge", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("gauge_test", 100, "foo", "bar"), + }, + }, + { + pts: []*testDataPoint{ + createDataPoint("gauge_test", 90, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "gauge_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + }, + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "gauge_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs + interval), Value: &metricspb.Point_DoubleValue{DoubleValue: 90.0}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "gauge-with-different-tags", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("gauge_test", 100, "foo", "bar"), + createDataPoint("gauge_test", 200, "bar", "foo"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "gauge_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "bar"}, {Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "", HasValue: false}, {Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + { + LabelValues: []*metricspb.LabelValue{{Value: "foo", HasValue: true}, {Value: "", HasValue: false}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 200.0}}, + }, + }, + }, + }, + }, + }, + }, + { + // TODO: A decision need to be made. If we want to have the behavior which can generate different tag key + // sets because metrics come and go + name: "gauge-comes-and-go-with-different-tagset", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("gauge_test", 100, "foo", "bar"), + createDataPoint("gauge_test", 200, "bar", "foo"), + }, + }, + { + pts: []*testDataPoint{ + createDataPoint("gauge_test", 20, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "gauge_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "bar"}, {Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "", HasValue: false}, {Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + { + LabelValues: []*metricspb.LabelValue{{Value: "foo", HasValue: true}, {Value: "", HasValue: false}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 200.0}}, + }, + }, + }, + }, + }, + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "gauge_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs + interval), Value: &metricspb.Point_DoubleValue{DoubleValue: 20.0}}, + }, + }, + }, + }, + }, + }, + }, + } + + runBuilderTests(t, tests) +} + +func Test_metricBuilder_untype(t *testing.T) { + tests := []buildTestData{ + { + name: "one-unknown", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("unknown_test", 100, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "unknown_test", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "no-type-hint", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("something_not_exists", 100, "foo", "bar"), + createDataPoint("theother_not_exists", 200, "foo", "bar"), + createDataPoint("theother_not_exists", 300, "bar", "foo"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "something_not_exists", + Type: metricspb.MetricDescriptor_UNSPECIFIED, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "theother_not_exists", + Type: metricspb.MetricDescriptor_UNSPECIFIED, + LabelKeys: []*metricspb.LabelKey{{Key: "bar"}, {Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "", HasValue: false}, {Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 200.0}}, + }, + }, + { + LabelValues: []*metricspb.LabelValue{{Value: "foo", HasValue: true}, {Value: "", HasValue: false}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 300.0}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "untype-metric-poor-names", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("some_count", 100, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "some_count", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DoubleValue{DoubleValue: 100.0}}, + }, + }, + }, + }, + }, + }, + }, + } + + runBuilderTests(t, tests) +} + +func Test_metricBuilder_histogram(t *testing.T) { + tests := []buildTestData{ + { + name: "single item", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 1, "foo", "bar", "le", "10"), + createDataPoint("hist_test", 2, "foo", "bar", "le", "20"), + createDataPoint("hist_test", 10, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, "foo", "bar"), + createDataPoint("hist_test_count", 10, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 10, + Sum: 99.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 8}}, + }}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "multi-groups", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 1, "foo", "bar", "le", "10"), + createDataPoint("hist_test", 2, "foo", "bar", "le", "20"), + createDataPoint("hist_test", 10, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, "foo", "bar"), + createDataPoint("hist_test_count", 10, "foo", "bar"), + createDataPoint("hist_test", 1, "key2", "v2", "le", "10"), + createDataPoint("hist_test", 2, "key2", "v2", "le", "20"), + createDataPoint("hist_test", 3, "key2", "v2", "le", "+inf"), + createDataPoint("hist_test_sum", 50, "key2", "v2"), + createDataPoint("hist_test_count", 3, "key2", "v2"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}, {Key: "key2"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}, {Value: "", HasValue: false}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 10, + Sum: 99.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 8}}, + }}}, + }, + }, + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "", HasValue: false}, {Value: "v2", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 3, + Sum: 50.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 1}}, + }}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "multi-groups-and-families", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 1, "foo", "bar", "le", "10"), + createDataPoint("hist_test", 2, "foo", "bar", "le", "20"), + createDataPoint("hist_test", 10, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, "foo", "bar"), + createDataPoint("hist_test_count", 10, "foo", "bar"), + createDataPoint("hist_test", 1, "key2", "v2", "le", "10"), + createDataPoint("hist_test", 2, "key2", "v2", "le", "20"), + createDataPoint("hist_test", 3, "key2", "v2", "le", "+inf"), + createDataPoint("hist_test_sum", 50, "key2", "v2"), + createDataPoint("hist_test_count", 3, "key2", "v2"), + createDataPoint("hist_test2", 1, "le", "10"), + createDataPoint("hist_test2", 2, "le", "20"), + createDataPoint("hist_test2", 3, "le", "+inf"), + createDataPoint("hist_test2_sum", 50), + createDataPoint("hist_test2_count", 3), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}, {Key: "key2"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}, {Value: "", HasValue: false}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 10, + Sum: 99.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 8}}, + }}}, + }, + }, + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "", HasValue: false}, {Value: "v2", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 3, + Sum: 50.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 1}}, + }}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test2", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 3, + Sum: 50.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 1}}, + }}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "unordered-buckets", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 10, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test", 1, "foo", "bar", "le", "10"), + createDataPoint("hist_test", 2, "foo", "bar", "le", "20"), + createDataPoint("hist_test_sum", 99, "foo", "bar"), + createDataPoint("hist_test_count", 10, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{10, 20}, + }, + }, + }, + Count: 10, + Sum: 99.0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 1}, {Count: 1}, {Count: 8}}, + }}}, + }, + }, + }, + }, + }, + }, + }, + { + // this won't likely happen in real env, as prometheus wont generate histogram with less than 3 buckets + name: "only-one-bucket", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 3, "le", "+inf"), + createDataPoint("hist_test_count", 3), + createDataPoint("hist_test_sum", 100), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{}, + }, + }, + }, + Count: 3, + Sum: 100, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 3}}, + }}}, + }, + }, + }, + }, + }, + }, + }, + { + // this won't likely happen in real env, as prometheus wont generate histogram with less than 3 buckets + name: "only-one-bucket-noninf", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 3, "le", "20"), + createDataPoint("hist_test_count", 3), + createDataPoint("hist_test_sum", 100), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "hist_test", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*metricspb.LabelKey{}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{}, + }, + }, + }, + Count: 3, + Sum: 100, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 3}}, + }}}, + }, + }, + }, + }, + }, + }, + }, + { + name: "corrupted-no-buckets", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test_sum", 99), + createDataPoint("hist_test_count", 10), + }, + }, + }, + wants: [][]*metricspb.Metric{ + {}, + }, + }, + { + name: "corrupted-no-sum", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 1, "foo", "bar", "le", "10"), + createDataPoint("hist_test", 2, "foo", "bar", "le", "20"), + createDataPoint("hist_test", 3, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_count", 3), + }, + }, + }, + wants: [][]*metricspb.Metric{ + {}, + }, + }, + { + name: "corrupted-no-count", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("hist_test", 1, "foo", "bar", "le", "10"), + createDataPoint("hist_test", 2, "foo", "bar", "le", "20"), + createDataPoint("hist_test", 3, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99), + }, + }, + }, + wants: [][]*metricspb.Metric{ + {}, + }, + }, + } + + runBuilderTests(t, tests) +} + +func Test_metricBuilder_summary(t *testing.T) { + tests := []buildTestData{ + { + name: "no-sum-and-count", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("summary_test", 5, "foo", "bar", "quantile", "1"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + {}, + }, + }, + { + name: "empty-quantiles", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("summary_test_sum", 100, "foo", "bar"), + createDataPoint("summary_test_count", 500, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "summary_test", + Type: metricspb.MetricDescriptor_SUMMARY, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + { + Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrapperspb.DoubleValue{Value: 100.0}, + Count: &wrapperspb.Int64Value{Value: 500}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "regular-summary", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("summary_test", 1, "foo", "bar", "quantile", "0.5"), + createDataPoint("summary_test", 2, "foo", "bar", "quantile", "0.75"), + createDataPoint("summary_test", 5, "foo", "bar", "quantile", "1"), + createDataPoint("summary_test_sum", 100, "foo", "bar"), + createDataPoint("summary_test_count", 500, "foo", "bar"), + }, + }, + }, + wants: [][]*metricspb.Metric{ + { + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "summary_test", + Type: metricspb.MetricDescriptor_SUMMARY, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}}, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: timestampFromMs(startTs), + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + {Timestamp: timestampFromMs(startTs), Value: &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrapperspb.DoubleValue{Value: 100.0}, + Count: &wrapperspb.Int64Value{Value: 500}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ + {Percentile: 50.0, Value: 1}, + {Percentile: 75.0, Value: 2}, + {Percentile: 100.0, Value: 5}, + }, + }}}}, + }, + }, + }, + }, + }, + }, + }, + } + + runBuilderTests(t, tests) +} + +func Test_metricBuilder_skipped(t *testing.T) { + tests := []buildTestData{ + { + name: "skip-internal-metrics", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint("scrape_foo", 1), + createDataPoint("up", 1.0), + }, + }, + { + pts: []*testDataPoint{ + createDataPoint("scrape_foo", 2), + createDataPoint("up", 2.0), + }, + }, + }, + wants: [][]*metricspb.Metric{ + {}, + {}, + }, + }, + } + + runBuilderTests(t, tests) +} + +func Test_metricBuilder_baddata(t *testing.T) { + t.Run("empty-metric-name", func(t *testing.T) { + mc := newMockMetadataCache(testMetadata) + b := newMetricBuilder(mc, true, "", testLogger) + b.startTime = 1.0 // set to a non-zero value + if err := b.AddDataPoint(labels.FromStrings("a", "b"), startTs, 123); err != errMetricNameNotFound { + t.Error("expecting errMetricNameNotFound error, but get nil") + return + } + + if _, _, _, err := b.Build(); err != errNoDataToBuild { + t.Error("expecting errNoDataToBuild error, but get nil") + } + }) + + t.Run("histogram-datapoint-no-bucket-label", func(t *testing.T) { + mc := newMockMetadataCache(testMetadata) + b := newMetricBuilder(mc, true, "", testLogger) + b.startTime = 1.0 // set to a non-zero value + if err := b.AddDataPoint(createLabels("hist_test", "k", "v"), startTs, 123); err != errEmptyBoundaryLabel { + t.Error("expecting errEmptyBoundaryLabel error, but get nil") + } + }) + + t.Run("summary-datapoint-no-quantile-label", func(t *testing.T) { + mc := newMockMetadataCache(testMetadata) + b := newMetricBuilder(mc, true, "", testLogger) + b.startTime = 1.0 // set to a non-zero value + if err := b.AddDataPoint(createLabels("summary_test", "k", "v"), startTs, 123); err != errEmptyBoundaryLabel { + t.Error("expecting errEmptyBoundaryLabel error, but get nil") + } + }) + +} + +func Test_isUsefulLabel(t *testing.T) { + type args struct { + mType metricspb.MetricDescriptor_Type + labelKey string + } + tests := []struct { + name string + args args + want bool + }{ + {"metricName", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.MetricNameLabel}, false}, + {"instance", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.InstanceLabel}, false}, + {"scheme", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.SchemeLabel}, false}, + {"metricPath", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.MetricsPathLabel}, false}, + {"job", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.JobLabel}, false}, + {"bucket", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.BucketLabel}, true}, + {"bucketForGaugeDistribution", args{metricspb.MetricDescriptor_GAUGE_DISTRIBUTION, model.BucketLabel}, false}, + {"bucketForCumulativeDistribution", args{metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, model.BucketLabel}, false}, + {"Quantile", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, model.QuantileLabel}, true}, + {"QuantileForSummay", args{metricspb.MetricDescriptor_SUMMARY, model.QuantileLabel}, false}, + {"other", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, "other"}, true}, + {"empty", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, ""}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isUsefulLabel(tt.args.mType, tt.args.labelKey); got != tt.want { + t.Errorf("isUsefulLabel() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_dpgSignature(t *testing.T) { + knownLabelKeys := []string{"a", "b"} + + tests := []struct { + name string + ls labels.Labels + want string + }{ + {"1st label", labels.FromStrings("a", "va"), `[]string{"a=va"}`}, + {"2nd label", labels.FromStrings("b", "vb"), `[]string{"b=vb"}`}, + {"two labels", labels.FromStrings("a", "va", "b", "vb"), `[]string{"a=va", "b=vb"}`}, + {"extra label", labels.FromStrings("a", "va", "b", "vb", "x", "xa"), `[]string{"a=va", "b=vb"}`}, + {"different order", labels.FromStrings("b", "vb", "a", "va"), `[]string{"a=va", "b=vb"}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := dpgSignature(knownLabelKeys, tt.ls); got != tt.want { + t.Errorf("dpgSignature() = %v, want %v", got, tt.want) + } + }) + } + + // this is important for caching start values, as new metrics with new tag of a same group can come up in a 2nd run, + // however, its order within the group is not predictable. we need to have a way to generate a stable key even if + // the total number of keys changes in between different scrape runs + t.Run("knownLabelKeys updated", func(t *testing.T) { + ls := labels.FromStrings("a", "va") + want := dpgSignature(knownLabelKeys, ls) + got := dpgSignature(append(knownLabelKeys, "c"), ls) + if got != want { + t.Errorf("dpgSignature() = %v, want %v", got, want) + } + }) +} + +func Test_normalizeMetricName(t *testing.T) { + tests := []struct { + name string + mname string + want string + }{ + {"normal", "normal", "normal"}, + {"count", "foo_count", "foo"}, + {"bucket", "foo_bucket", "foo"}, + {"sum", "foo_sum", "foo"}, + {"no_prefix", "_sum", "_sum"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := normalizeMetricName(tt.mname); got != tt.want { + t.Errorf("normalizeMetricName() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getBoundary(t *testing.T) { + ls := labels.FromStrings("le", "100.0", "foo", "bar", "quantile", "0.5") + ls2 := labels.FromStrings("foo", "bar") + ls3 := labels.FromStrings("le", "xyz", "foo", "bar", "quantile", "0.5") + type args struct { + metricType metricspb.MetricDescriptor_Type + labels labels.Labels + } + tests := []struct { + name string + args args + want float64 + wantErr bool + }{ + {"histogram", args{metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, ls}, 100.0, false}, + {"gaugehistogram", args{metricspb.MetricDescriptor_GAUGE_DISTRIBUTION, ls}, 100.0, false}, + {"gaugehistogram_no_label", args{metricspb.MetricDescriptor_GAUGE_DISTRIBUTION, ls2}, 0, true}, + {"gaugehistogram_bad_value", args{metricspb.MetricDescriptor_GAUGE_DISTRIBUTION, ls3}, 0, true}, + {"summary", args{metricspb.MetricDescriptor_SUMMARY, ls}, 0.5, false}, + {"otherType", args{metricspb.MetricDescriptor_GAUGE_DOUBLE, ls}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getBoundary(tt.args.metricType, tt.args.labels) + if (err != nil) != tt.wantErr { + t.Errorf("getBoundary() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getBoundary() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_convToOCAMetricType(t *testing.T) { + tests := []struct { + name string + metricType textparse.MetricType + want metricspb.MetricDescriptor_Type + }{ + {"counter", textparse.MetricTypeCounter, metricspb.MetricDescriptor_CUMULATIVE_DOUBLE}, + {"gauge", textparse.MetricTypeGauge, metricspb.MetricDescriptor_GAUGE_DOUBLE}, + {"histogram", textparse.MetricTypeHistogram, metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION}, + {"guageHistogram", textparse.MetricTypeGaugeHistogram, metricspb.MetricDescriptor_UNSPECIFIED}, + {"summary", textparse.MetricTypeSummary, metricspb.MetricDescriptor_SUMMARY}, + {"info", textparse.MetricTypeInfo, metricspb.MetricDescriptor_UNSPECIFIED}, + {"stateset", textparse.MetricTypeStateset, metricspb.MetricDescriptor_UNSPECIFIED}, + {"unknown", textparse.MetricTypeUnknown, metricspb.MetricDescriptor_GAUGE_DOUBLE}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := convToOCAMetricType(tt.metricType); !reflect.DeepEqual(got, tt.want) { + t.Errorf("convToOCAMetricType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_heuristicalMetricAndKnownUnits(t *testing.T) { + tests := []struct { + metricName string + parsedUnit string + want string + }{ + {"test", "ms", "ms"}, + {"millisecond", "", ""}, + {"test_millisecond", "", "ms"}, + {"test_milliseconds", "", "ms"}, + {"test_ms", "", "ms"}, + {"test_second", "", "s"}, + {"test_seconds", "", "s"}, + {"test_s", "", "s"}, + {"test_microsecond", "", "us"}, + {"test_microseconds", "", "us"}, + {"test_us", "", "us"}, + {"test_nanosecond", "", "ns"}, + {"test_nanoseconds", "", "ns"}, + {"test_ns", "", "ns"}, + {"test_byte", "", "By"}, + {"test_bytes", "", "By"}, + {"test_by", "", "By"}, + {"test_bit", "", "Bi"}, + {"test_bits", "", "Bi"}, + {"test_kilogram", "", "kg"}, + {"test_kilograms", "", "kg"}, + {"test_kg", "", "kg"}, + {"test_gram", "", "g"}, + {"test_grams", "", "g"}, + {"test_g", "", "g"}, + {"test_nanogram", "", "ng"}, + {"test_nanograms", "", "ng"}, + {"test_ng", "", "ng"}, + {"test_meter", "", "m"}, + {"test_meters", "", "m"}, + {"test_metre", "", "m"}, + {"test_metres", "", "m"}, + {"test_m", "", "m"}, + {"test_kilometer", "", "km"}, + {"test_kilometers", "", "km"}, + {"test_kilometre", "", "km"}, + {"test_kilometres", "", "km"}, + {"test_km", "", "km"}, + {"test_milimeter", "", "mm"}, + {"test_milimeters", "", "mm"}, + {"test_milimetre", "", "mm"}, + {"test_milimetres", "", "mm"}, + {"test_mm", "", "mm"}, + } + for _, tt := range tests { + t.Run(tt.metricName, func(t *testing.T) { + if got := heuristicalMetricAndKnownUnits(tt.metricName, tt.parsedUnit); got != tt.want { + t.Errorf("heuristicalMetricAndKnownUnits() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/ocastore.go b/internal/otel_collector/receiver/prometheusreceiver/internal/ocastore.go new file mode 100644 index 00000000000..e019d9bf4be --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/ocastore.go @@ -0,0 +1,109 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "context" + "sync/atomic" + + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/scrape" + "github.com/prometheus/prometheus/storage" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" +) + +const ( + runningStateInit = iota + runningStateReady + runningStateStop +) + +var idSeq int64 +var noop = &noopAppender{} + +// OcaStore translates Prometheus scraping diffs into OpenCensus format. +type OcaStore struct { + ctx context.Context + + running int32 // access atomically + sink consumer.MetricsConsumer + mc *metadataService + jobsMap *JobsMap + useStartTimeMetric bool + startTimeMetricRegex string + receiverName string + + logger *zap.Logger +} + +// NewOcaStore returns an ocaStore instance, which can be acted as prometheus' scrape.Appendable +func NewOcaStore(ctx context.Context, sink consumer.MetricsConsumer, logger *zap.Logger, jobsMap *JobsMap, useStartTimeMetric bool, startTimeMetricRegex string, receiverName string) *OcaStore { + return &OcaStore{ + running: runningStateInit, + ctx: ctx, + sink: sink, + logger: logger, + jobsMap: jobsMap, + useStartTimeMetric: useStartTimeMetric, + startTimeMetricRegex: startTimeMetricRegex, + receiverName: receiverName, + } +} + +// SetScrapeManager is used to config the underlying scrape.Manager as it's needed for OcaStore, otherwise OcaStore +// cannot accept any Appender() request +func (o *OcaStore) SetScrapeManager(scrapeManager *scrape.Manager) { + if scrapeManager != nil && atomic.CompareAndSwapInt32(&o.running, runningStateInit, runningStateReady) { + o.mc = &metadataService{sm: scrapeManager} + } +} + +func (o *OcaStore) Appender(context.Context) storage.Appender { + state := atomic.LoadInt32(&o.running) + if state == runningStateReady { + return newTransaction(o.ctx, o.jobsMap, o.useStartTimeMetric, o.startTimeMetricRegex, o.receiverName, o.mc, o.sink, o.logger) + } else if state == runningStateInit { + panic("ScrapeManager is not set") + } + // instead of returning an error, return a dummy appender instead, otherwise it can trigger panic + return noop +} + +func (o *OcaStore) Close() error { + atomic.CompareAndSwapInt32(&o.running, runningStateReady, runningStateStop) + return nil +} + +// noopAppender, always return error on any operations +type noopAppender struct{} + +func (*noopAppender) Add(labels.Labels, int64, float64) (uint64, error) { + return 0, componenterror.ErrAlreadyStopped +} + +func (*noopAppender) AddFast(uint64, int64, float64) error { + return componenterror.ErrAlreadyStopped +} + +func (*noopAppender) Commit() error { + return componenterror.ErrAlreadyStopped +} + +func (*noopAppender) Rollback() error { + return nil +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/ocastore_test.go b/internal/otel_collector/receiver/prometheusreceiver/internal/ocastore_test.go new file mode 100644 index 00000000000..eb95b876b1a --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/ocastore_test.go @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "context" + "testing" + + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/scrape" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOcaStore(t *testing.T) { + + o := NewOcaStore(context.Background(), nil, nil, nil, false, "", "prometheus") + o.SetScrapeManager(&scrape.Manager{}) + + app := o.Appender(context.Background()) + require.NotNil(t, app, "Expecting app") + + _ = o.Close() + + app = o.Appender(context.Background()) + assert.Equal(t, noop, app) +} + +func TestNoopAppender(t *testing.T) { + if _, err := noop.Add(labels.FromStrings("t", "v"), 1, 1); err == nil { + t.Error("expecting error from Add method of noopApender") + } + if _, err := noop.Add(labels.FromStrings("t", "v"), 1, 1); err == nil { + t.Error("expecting error from Add method of noopApender") + } + + if err := noop.AddFast(0, 1, 1); err == nil { + t.Error("expecting error from AddFast method of noopApender") + } + + if err := noop.Commit(); err == nil { + t.Error("expecting error from Commit method of noopApender") + } + + if err := noop.Rollback(); err != nil { + t.Error("expecting no error from Rollback method of noopApender") + } + +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/transaction.go b/internal/otel_collector/receiver/prometheusreceiver/internal/transaction.go new file mode 100644 index 00000000000..bd40e2b0459 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/transaction.go @@ -0,0 +1,236 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "context" + "errors" + "math" + "net" + "sync/atomic" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/storage" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/translator/internaldata" +) + +const ( + portAttr = "port" + schemeAttr = "scheme" + + transport = "http" + dataformat = "prometheus" +) + +var errMetricNameNotFound = errors.New("metricName not found from labels") +var errTransactionAborted = errors.New("transaction aborted") +var errNoJobInstance = errors.New("job or instance cannot be found from labels") +var errNoStartTimeMetrics = errors.New("process_start_time_seconds metric is missing") + +// A transaction is corresponding to an individual scrape operation or stale report. +// That said, whenever prometheus receiver scrapped a target metric endpoint a page of raw metrics is returned, +// a transaction, which acts as appender, is created to process this page of data, the scrapeLoop will call the Add or +// AddFast method to insert metrics data points, when finished either Commit, which means success, is called and data +// will be flush to the downstream consumer, or Rollback, which means discard all the data, is called and all data +// points are discarded. +type transaction struct { + id int64 + ctx context.Context + isNew bool + sink consumer.MetricsConsumer + job string + instance string + jobsMap *JobsMap + useStartTimeMetric bool + startTimeMetricRegex string + receiverName string + ms *metadataService + node *commonpb.Node + resource *resourcepb.Resource + metricBuilder *metricBuilder + logger *zap.Logger +} + +func newTransaction(ctx context.Context, jobsMap *JobsMap, useStartTimeMetric bool, startTimeMetricRegex string, receiverName string, ms *metadataService, sink consumer.MetricsConsumer, logger *zap.Logger) *transaction { + return &transaction{ + id: atomic.AddInt64(&idSeq, 1), + ctx: ctx, + isNew: true, + sink: sink, + jobsMap: jobsMap, + useStartTimeMetric: useStartTimeMetric, + startTimeMetricRegex: startTimeMetricRegex, + receiverName: receiverName, + ms: ms, + logger: logger, + } +} + +// ensure *transaction has implemented the storage.Appender interface +var _ storage.Appender = (*transaction)(nil) + +// always returns 0 to disable label caching +func (tr *transaction) Add(ls labels.Labels, t int64, v float64) (uint64, error) { + // Important, must handle. prometheus will still try to feed the appender some data even if it failed to + // scrape the remote target, if the previous scrape was success and some data were cached internally + // in our case, we don't need these data, simply drop them shall be good enough. more details: + // https://github.com/prometheus/prometheus/blob/851131b0740be7291b98f295567a97f32fffc655/scrape/scrape.go#L933-L935 + if math.IsNaN(v) { + return 0, nil + } + + select { + case <-tr.ctx.Done(): + return 0, errTransactionAborted + default: + } + + if tr.isNew { + if err := tr.initTransaction(ls); err != nil { + return 0, err + } + } + return 0, tr.metricBuilder.AddDataPoint(ls, t, v) +} + +// always returns error since caching is not supported by Add() function +func (tr *transaction) AddFast(_ uint64, _ int64, _ float64) error { + return storage.ErrNotFound +} + +func (tr *transaction) initTransaction(ls labels.Labels) error { + job, instance := ls.Get(model.JobLabel), ls.Get(model.InstanceLabel) + if job == "" || instance == "" { + return errNoJobInstance + } + // discover the binding target when this method is called for the first time during a transaction + mc, err := tr.ms.Get(job, instance) + if err != nil { + return err + } + if tr.jobsMap != nil { + tr.job = job + tr.instance = instance + } + tr.node, tr.resource = createNodeAndResource(job, instance, mc.SharedLabels().Get(model.SchemeLabel)) + tr.metricBuilder = newMetricBuilder(mc, tr.useStartTimeMetric, tr.startTimeMetricRegex, tr.logger) + tr.isNew = false + return nil +} + +// submit metrics data to consumers +func (tr *transaction) Commit() error { + if tr.isNew { + // In a situation like not able to connect to the remote server, scrapeloop will still commit even if it had + // never added any data points, that the transaction has not been initialized. + return nil + } + + ctx := obsreport.StartMetricsReceiveOp(tr.ctx, tr.receiverName, transport) + metrics, _, _, err := tr.metricBuilder.Build() + if err != nil { + // Only error by Build() is errNoDataToBuild, with numReceivedPoints set to zero. + obsreport.EndMetricsReceiveOp(ctx, dataformat, 0, err) + return err + } + + if tr.useStartTimeMetric { + // startTime is mandatory in this case, but may be zero when the + // process_start_time_seconds metric is missing from the target endpoint. + if tr.metricBuilder.startTime == 0.0 { + // Since we are unable to adjust metrics properly, we will drop them + // and return an error. + err = errNoStartTimeMetrics + obsreport.EndMetricsReceiveOp(ctx, dataformat, 0, err) + return err + } + + adjustStartTime(tr.metricBuilder.startTime, metrics) + } else { + // AdjustMetrics - jobsMap has to be non-nil in this case. + // Note: metrics could be empty after adjustment, which needs to be checked before passing it on to ConsumeMetricsData() + metrics, _ = NewMetricsAdjuster(tr.jobsMap.get(tr.job, tr.instance), tr.logger).AdjustMetrics(metrics) + } + + numPoints := 0 + if len(metrics) > 0 { + md := internaldata.OCToMetrics(consumerdata.MetricsData{ + Node: tr.node, + Resource: tr.resource, + Metrics: metrics, + }) + _, numPoints = md.MetricAndDataPointCount() + err = tr.sink.ConsumeMetrics(ctx, md) + } + obsreport.EndMetricsReceiveOp(ctx, dataformat, numPoints, err) + return err +} + +func (tr *transaction) Rollback() error { + return nil +} + +func adjustStartTime(startTime float64, metrics []*metricspb.Metric) { + startTimeTs := timestampFromFloat64(startTime) + for _, metric := range metrics { + switch metric.GetMetricDescriptor().GetType() { + case metricspb.MetricDescriptor_GAUGE_DOUBLE, metricspb.MetricDescriptor_GAUGE_DISTRIBUTION: + continue + default: + for _, ts := range metric.GetTimeseries() { + ts.StartTimestamp = startTimeTs + } + } + } +} + +func timestampFromFloat64(ts float64) *timestamppb.Timestamp { + secs := int64(ts) + nanos := int64((ts - float64(secs)) * 1e9) + return ×tamppb.Timestamp{ + Seconds: secs, + Nanos: int32(nanos), + } +} + +func createNodeAndResource(job, instance, scheme string) (*commonpb.Node, *resourcepb.Resource) { + host, port, err := net.SplitHostPort(instance) + if err != nil { + host = instance + } + node := &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: job}, + Identifier: &commonpb.ProcessIdentifier{ + HostName: host, + }, + } + resource := &resourcepb.Resource{ + Labels: map[string]string{ + portAttr: port, + schemeAttr: scheme, + }, + } + return node, resource +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/internal/transaction_test.go b/internal/otel_collector/receiver/prometheusreceiver/internal/transaction_test.go new file mode 100644 index 00000000000..d52737c7fb6 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/internal/transaction_test.go @@ -0,0 +1,163 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "context" + "math" + "testing" + "time" + + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/scrape" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/translator/internaldata" +) + +func Test_transaction(t *testing.T) { + // discoveredLabels contain labels prior to any processing + discoveredLabels := labels.New( + labels.Label{ + Name: model.AddressLabel, + Value: "address:8080", + }, + labels.Label{ + Name: model.MetricNameLabel, + Value: "foo", + }, + labels.Label{ + Name: model.SchemeLabel, + Value: "http", + }, + ) + // processedLabels contain label values after processing (e.g. relabeling) + processedLabels := labels.New( + labels.Label{ + Name: model.InstanceLabel, + Value: "localhost:8080", + }, + ) + + ms := &metadataService{ + sm: &mockScrapeManager{targets: map[string][]*scrape.Target{ + "test": {scrape.NewTarget(processedLabels, discoveredLabels, nil)}, + }}, + } + + rn := "prometheus" + + t.Run("Commit Without Adding", func(t *testing.T) { + nomc := consumertest.NewMetricsNop() + tr := newTransaction(context.Background(), nil, true, "", rn, ms, nomc, testLogger) + if got := tr.Commit(); got != nil { + t.Errorf("expecting nil from Commit() but got err %v", got) + } + }) + + t.Run("Rollback dose nothing", func(t *testing.T) { + nomc := consumertest.NewMetricsNop() + tr := newTransaction(context.Background(), nil, true, "", rn, ms, nomc, testLogger) + if got := tr.Rollback(); got != nil { + t.Errorf("expecting nil from Rollback() but got err %v", got) + } + }) + + badLabels := labels.Labels([]labels.Label{{Name: "foo", Value: "bar"}}) + t.Run("Add One No Target", func(t *testing.T) { + nomc := consumertest.NewMetricsNop() + tr := newTransaction(context.Background(), nil, true, "", rn, ms, nomc, testLogger) + if _, got := tr.Add(badLabels, time.Now().Unix()*1000, 1.0); got == nil { + t.Errorf("expecting error from Add() but got nil") + } + }) + + jobNotFoundLb := labels.Labels([]labels.Label{ + {Name: "instance", Value: "localhost:8080"}, + {Name: "job", Value: "test2"}, + {Name: "foo", Value: "bar"}}) + t.Run("Add One Job not found", func(t *testing.T) { + nomc := consumertest.NewMetricsNop() + tr := newTransaction(context.Background(), nil, true, "", rn, ms, nomc, testLogger) + if _, got := tr.Add(jobNotFoundLb, time.Now().Unix()*1000, 1.0); got == nil { + t.Errorf("expecting error from Add() but got nil") + } + }) + + goodLabels := labels.Labels([]labels.Label{{Name: "instance", Value: "localhost:8080"}, + {Name: "job", Value: "test"}, + {Name: "__name__", Value: "foo"}}) + t.Run("Add One Good", func(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(context.Background(), nil, true, "", rn, ms, sink, testLogger) + if _, got := tr.Add(goodLabels, time.Now().Unix()*1000, 1.0); got != nil { + t.Errorf("expecting error == nil from Add() but got: %v\n", got) + } + tr.metricBuilder.startTime = 1.0 // set to a non-zero value + if got := tr.Commit(); got != nil { + t.Errorf("expecting nil from Commit() but got err %v", got) + } + expectedNode, expectedResource := createNodeAndResource("test", "localhost:8080", "http") + mds := sink.AllMetrics() + if len(mds) != 1 { + t.Fatalf("wanted one batch, got %v\n", sink.AllMetrics()) + } + ocmds := internaldata.MetricsToOC(mds[0]) + if len(ocmds) != 1 { + t.Fatalf("wanted one batch per node, got %v\n", sink.AllMetrics()) + } + if !proto.Equal(ocmds[0].Node, expectedNode) { + t.Errorf("generated node %v and expected node %v is different\n", ocmds[0].Node, expectedNode) + } + if !proto.Equal(ocmds[0].Resource, expectedResource) { + t.Errorf("generated resource %v and expected resource %v is different\n", ocmds[0].Resource, expectedResource) + } + + // TODO: re-enable this when handle unspecified OC type + // assert.Len(t, ocmds[0].Metrics, 1) + }) + + t.Run("Error when start time is zero", func(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(context.Background(), nil, true, "", rn, ms, sink, testLogger) + if _, got := tr.Add(goodLabels, time.Now().Unix()*1000, 1.0); got != nil { + t.Errorf("expecting error == nil from Add() but got: %v\n", got) + } + tr.metricBuilder.startTime = 0 // zero value means the start time metric is missing + got := tr.Commit() + if got == nil { + t.Error("expecting error from Commit() but got nil") + } else if got.Error() != errNoStartTimeMetrics.Error() { + t.Errorf("expected error %q but got %q", errNoStartTimeMetrics, got) + } + }) + + t.Run("Drop NaN value", func(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(context.Background(), nil, true, "", rn, ms, sink, testLogger) + if _, got := tr.Add(goodLabels, time.Now().Unix()*1000, math.NaN()); got != nil { + t.Errorf("expecting error == nil from Add() but got: %v\n", got) + } + if got := tr.Commit(); got != nil { + t.Errorf("expecting nil from Commit() but got err %v", got) + } + if len(sink.AllMetrics()) != 0 { + t.Errorf("wanted nil, got %v\n", sink.AllMetrics()) + } + }) + +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/metrics_receiver.go b/internal/otel_collector/receiver/prometheusreceiver/metrics_receiver.go new file mode 100644 index 00000000000..3c9828b154e --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/metrics_receiver.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusreceiver + +import ( + "context" + "time" + + "github.com/prometheus/prometheus/discovery" + "github.com/prometheus/prometheus/scrape" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/prometheusreceiver/internal" +) + +// pReceiver is the type that provides Prometheus scraper/receiver functionality. +type pReceiver struct { + cfg *Config + consumer consumer.MetricsConsumer + cancelFunc context.CancelFunc + + logger *zap.Logger +} + +// New creates a new prometheus.Receiver reference. +func newPrometheusReceiver(logger *zap.Logger, cfg *Config, next consumer.MetricsConsumer) *pReceiver { + pr := &pReceiver{ + cfg: cfg, + consumer: next, + logger: logger, + } + return pr +} + +// Start is the method that starts Prometheus scraping and it +// is controlled by having previously defined a Configuration using perhaps New. +func (r *pReceiver) Start(ctx context.Context, host component.Host) error { + discoveryCtx, cancel := context.WithCancel(context.Background()) + r.cancelFunc = cancel + + logger := internal.NewZapToGokitLogAdapter(r.logger) + + discoveryManager := discovery.NewManager(discoveryCtx, logger) + discoveryCfg := make(map[string]discovery.Configs) + for _, scrapeConfig := range r.cfg.PrometheusConfig.ScrapeConfigs { + discoveryCfg[scrapeConfig.JobName] = scrapeConfig.ServiceDiscoveryConfigs + } + if err := discoveryManager.ApplyConfig(discoveryCfg); err != nil { + return err + } + go func() { + if err := discoveryManager.Run(); err != nil { + r.logger.Error("Discovery manager failed", zap.Error(err)) + host.ReportFatalError(err) + } + }() + + var jobsMap *internal.JobsMap + if !r.cfg.UseStartTimeMetric { + jobsMap = internal.NewJobsMap(2 * time.Minute) + } + ocaStore := internal.NewOcaStore(ctx, r.consumer, r.logger, jobsMap, r.cfg.UseStartTimeMetric, r.cfg.StartTimeMetricRegex, r.cfg.Name()) + + scrapeManager := scrape.NewManager(logger, ocaStore) + ocaStore.SetScrapeManager(scrapeManager) + if err := scrapeManager.ApplyConfig(r.cfg.PrometheusConfig); err != nil { + return err + } + go func() { + if err := scrapeManager.Run(discoveryManager.SyncCh()); err != nil { + r.logger.Error("Scrape manager failed", zap.Error(err)) + host.ReportFatalError(err) + } + }() + return nil +} + +// Shutdown stops and cancels the underlying Prometheus scrapers. +func (r *pReceiver) Shutdown(context.Context) error { + r.cancelFunc() + return nil +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/metrics_receiver_test.go b/internal/otel_collector/receiver/prometheusreceiver/metrics_receiver_test.go new file mode 100644 index 00000000000..14bfff93c6e --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/metrics_receiver_test.go @@ -0,0 +1,1183 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prometheusreceiver + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "sync/atomic" + "testing" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/golang/protobuf/ptypes/wrappers" + promcfg "github.com/prometheus/prometheus/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" + "gopkg.in/yaml.v2" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/translator/internaldata" +) + +var logger = zap.NewNop() + +type mockPrometheusResponse struct { + code int + data string +} + +type mockPrometheus struct { + endpoints map[string][]mockPrometheusResponse + accessIndex map[string]*int32 + wg *sync.WaitGroup + srv *httptest.Server +} + +func newMockPrometheus(endpoints map[string][]mockPrometheusResponse) *mockPrometheus { + accessIndex := make(map[string]*int32) + wg := &sync.WaitGroup{} + wg.Add(len(endpoints)) + for k := range endpoints { + v := int32(0) + accessIndex[k] = &v + } + mp := &mockPrometheus{ + wg: wg, + accessIndex: accessIndex, + endpoints: endpoints, + } + srv := httptest.NewServer(mp) + mp.srv = srv + return mp +} + +func (mp *mockPrometheus) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + iptr, ok := mp.accessIndex[req.URL.Path] + if !ok { + rw.WriteHeader(404) + return + } + index := int(*iptr) + atomic.AddInt32(iptr, 1) + pages := mp.endpoints[req.URL.Path] + if index >= len(pages) { + if index == len(pages) { + mp.wg.Done() + } + rw.WriteHeader(404) + return + } + rw.WriteHeader(pages[index].code) + _, _ = rw.Write([]byte(pages[index].data)) +} + +func (mp *mockPrometheus) Close() { + mp.srv.Close() +} + +// ------------------------- +// EndToEnd Test and related +// ------------------------- + +var srvPlaceHolder = "__SERVER_ADDRESS__" + +type testData struct { + name string + pages []mockPrometheusResponse + node *commonpb.Node + resource *resourcepb.Resource + validateFunc func(t *testing.T, td *testData, result []consumerdata.MetricsData) +} + +// setupMockPrometheus to create a mocked prometheus based on targets, returning the server and a prometheus exporting +// config +func setupMockPrometheus(tds ...*testData) (*mockPrometheus, *promcfg.Config, error) { + jobs := make([]map[string]interface{}, 0, len(tds)) + endpoints := make(map[string][]mockPrometheusResponse) + for _, t := range tds { + metricPath := fmt.Sprintf("/%s/metrics", t.name) + endpoints[metricPath] = t.pages + job := make(map[string]interface{}) + job["job_name"] = t.name + job["metrics_path"] = metricPath + job["scrape_interval"] = "1s" + job["static_configs"] = []map[string]interface{}{{"targets": []string{srvPlaceHolder}}} + jobs = append(jobs, job) + } + + if len(jobs) != len(tds) { + log.Fatal("len(jobs) != len(targets), make sure job names are unique") + } + config := make(map[string]interface{}) + config["scrape_configs"] = jobs + + mp := newMockPrometheus(endpoints) + cfg, err := yaml.Marshal(&config) + if err != nil { + return mp, nil, err + } + u, _ := url.Parse(mp.srv.URL) + host, port, _ := net.SplitHostPort(u.Host) + + // update node value (will use for validation) + for _, t := range tds { + t.node = &commonpb.Node{ + Identifier: &commonpb.ProcessIdentifier{ + HostName: host, + }, + ServiceInfo: &commonpb.ServiceInfo{ + Name: t.name, + }, + } + t.resource = &resourcepb.Resource{ + Labels: map[string]string{ + "scheme": "http", + "port": port, + }, + } + } + + cfgStr := strings.ReplaceAll(string(cfg), srvPlaceHolder, u.Host) + pCfg, err := promcfg.Load(cfgStr) + return mp, pCfg, err +} + +func verifyNumScrapeResults(t *testing.T, td *testData, mds []consumerdata.MetricsData) { + want := 0 + for _, p := range td.pages { + if p.code == 200 { + want++ + } + } + if l := len(mds); l != want { + t.Errorf("want %d, but got %d\n", want, l) + } +} + +func doCompare(name string, t *testing.T, want, got interface{}) { + t.Run(name, func(t *testing.T) { + assert.EqualValues(t, want, got) + }) +} + +// Test data and validation functions for EndToEnd test +// Make sure every page has a gauge, we are relying on it to figure out the starttime if needed + +// target1 has one gague, two counts of a same family, one histogram and one summary. We are expecting the first +// successful scrape will produce a metric with only the gauge metric, and the 2nd successful scrape will produce all +// the metrics, with the cumulative types using the firstpage's timestamp as starttime, and values are deltas with the +// one from the first page (summary quantiles will be as it is) +var target1Page1 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 19 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 100 +http_requests_total{method="post",code="400"} 5 + +# HELP http_request_duration_seconds A histogram of the request duration. +# TYPE http_request_duration_seconds histogram +http_request_duration_seconds_bucket{le="0.05"} 1000 +http_request_duration_seconds_bucket{le="0.5"} 1500 +http_request_duration_seconds_bucket{le="1"} 2000 +http_request_duration_seconds_bucket{le="+Inf"} 2500 +http_request_duration_seconds_sum 5000 +http_request_duration_seconds_count 2500 + +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{quantile="0.01"} 1 +rpc_duration_seconds{quantile="0.9"} 5 +rpc_duration_seconds{quantile="0.99"} 8 +rpc_duration_seconds_sum 5000 +rpc_duration_seconds_count 1000 +` + +var target1Page2 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 18 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 199 +http_requests_total{method="post",code="400"} 12 + +# HELP http_request_duration_seconds A histogram of the request duration. +# TYPE http_request_duration_seconds histogram +http_request_duration_seconds_bucket{le="0.05"} 1100 +http_request_duration_seconds_bucket{le="0.5"} 1600 +http_request_duration_seconds_bucket{le="1"} 2100 +http_request_duration_seconds_bucket{le="+Inf"} 2600 +http_request_duration_seconds_sum 5050 +http_request_duration_seconds_count 2600 + +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{quantile="0.01"} 1 +rpc_duration_seconds{quantile="0.9"} 6 +rpc_duration_seconds{quantile="0.99"} 8 +rpc_duration_seconds_sum 5002 +rpc_duration_seconds_count 1001 +` + +func verifyTarget1(t *testing.T, td *testData, mds []consumerdata.MetricsData) { + verifyNumScrapeResults(t, td, mds) + m1 := mds[0] + // m1 shall only have a gauge + if l := len(m1.Metrics); l != 1 { + t.Errorf("want 1, but got %v\n", l) + } + + // only gauge value is returned from the first scrape + wantG1 := &metricspb.Metric{ + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Value: &metricspb.Point_DoubleValue{DoubleValue: 19.0}}, + }, + }, + }, + } + gotG1 := m1.Metrics[0] + // relying on the timestamps from gagues as startTimestamps + ts1 := gotG1.Timeseries[0].Points[0].Timestamp + // set this timestamp to wantG1 + wantG1.Timeseries[0].Points[0].Timestamp = ts1 + doCompare("scrape1", t, wantG1, gotG1) + + // verify the 2nd metricData + m2 := mds[1] + ts2 := m2.Metrics[0].Timeseries[0].Points[0].Timestamp + + want2 := &consumerdata.MetricsData{ + Node: td.node, + Resource: td.resource, + Metrics: []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE}, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 18.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_requests_total", + Description: "The total number of HTTP requests.", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "code"}, {Key: "method"}}, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{ + {Value: "200", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 99.0}}, + }, + }, + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{ + {Value: "400", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 7.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_request_duration_seconds", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + Description: "A histogram of the request duration.", + Unit: "s", + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + Points: []*metricspb.Point{ + { + Timestamp: ts2, + Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{0.05, 0.5, 1}, + }, + }, + }, + Count: 100, + Sum: 50.0, + Buckets: []*metricspb.DistributionValue_Bucket{ + {Count: 100}, + {Count: 0}, + {Count: 0}, + {Count: 0}, + }, + }}, + }, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "rpc_duration_seconds", + Type: metricspb.MetricDescriptor_SUMMARY, + Description: "A summary of the RPC duration in seconds.", + Unit: "s", + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + Points: []*metricspb.Point{ + { + Timestamp: ts2, + Value: &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrappers.DoubleValue{Value: 2}, + Count: &wrappers.Int64Value{Value: 1}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ + { + Percentile: 1, + Value: 1, + }, + { + Percentile: 90, + Value: 6, + }, + { + Percentile: 99, + Value: 8, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + doCompare("scrape2", t, want2, &m2) +} + +// target2 is going to have 5 pages, and there's a newly appeared item from the 2nd page. we are expecting the new +// metric will appears on the 3rd scrape, and it uses the timestamp from the 2nd page as starttime, and value is delta +// from the 2nd page. +// with the 4th page, we are simulating a reset (values smaller than previous), cumulative types shall be skipped at +// this run. with the 5th page, cumulative types will be delta with the 4th one +var target2Page1 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 18 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 10 +http_requests_total{method="post",code="400"} 50 +` + +var target2Page2 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 16 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 50 +http_requests_total{method="post",code="400"} 60 +http_requests_total{method="post",code="500"} 3 +` + +var target2Page3 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 16 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 50 +http_requests_total{method="post",code="400"} 60 +http_requests_total{method="post",code="500"} 5 +` + +var target2Page4 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 16 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 49 +http_requests_total{method="post",code="400"} 59 +http_requests_total{method="post",code="500"} 3 +` + +var target2Page5 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 16 + +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 50 +http_requests_total{method="post",code="400"} 59 +http_requests_total{method="post",code="500"} 5 +` + +func verifyTarget2(t *testing.T, td *testData, mds []consumerdata.MetricsData) { + verifyNumScrapeResults(t, td, mds) + m1 := mds[0] + // m1 shall only have a gauge + if l := len(m1.Metrics); l != 1 { + t.Errorf("want 1, but got %v\n", l) + } + + // only gauge value is returned from the first scrape + wantG1 := &metricspb.Metric{ + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Value: &metricspb.Point_DoubleValue{DoubleValue: 18.0}}, + }, + }, + }, + } + gotG1 := m1.Metrics[0] + ts1 := gotG1.Timeseries[0].Points[0].Timestamp + // set this timestamp to wantG1 + wantG1.Timeseries[0].Points[0].Timestamp = ts1 + doCompare("scrape1", t, wantG1, gotG1) + + // verify the 2nd metricData + m2 := mds[1] + ts2 := m2.Metrics[0].Timeseries[0].Points[0].Timestamp + + want2 := &consumerdata.MetricsData{ + Node: td.node, + Resource: td.resource, + Metrics: []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 16.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_requests_total", + Description: "The total number of HTTP requests.", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "code"}, {Key: "method"}}, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{ + {Value: "200", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 40.0}}, + }, + }, + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{ + {Value: "400", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 10.0}}, + }, + }, + }, + }, + }, + } + doCompare("scrape2", t, want2, &m2) + + // verify the 3rd metricData, with the new code=500 counter which first appeared on 2nd run + m3 := mds[2] + // its start timestamp shall be from the 2nd run + ts3 := m3.Metrics[0].Timeseries[0].Points[0].Timestamp + + want3 := &consumerdata.MetricsData{ + Node: td.node, + Resource: td.resource, + Metrics: []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Timestamp: ts3, Value: &metricspb.Point_DoubleValue{DoubleValue: 16.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_requests_total", + Description: "The total number of HTTP requests.", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "code"}, {Key: "method"}}, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{ + {Value: "200", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts3, Value: &metricspb.Point_DoubleValue{DoubleValue: 40.0}}, + }, + }, + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{ + {Value: "400", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts3, Value: &metricspb.Point_DoubleValue{DoubleValue: 10.0}}, + }, + }, + { + StartTimestamp: ts2, + LabelValues: []*metricspb.LabelValue{ + {Value: "500", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts3, Value: &metricspb.Point_DoubleValue{DoubleValue: 2.0}}, + }, + }, + }, + }, + }, + } + doCompare("scrape3", t, want3, &m3) + + // verify the 4th metricData which reset happens, all cumulative types shall be absent + m4 := mds[3] + ts4 := m4.Metrics[0].Timeseries[0].Points[0].Timestamp + + want4 := &consumerdata.MetricsData{ + Node: td.node, + Resource: td.resource, + Metrics: []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Timestamp: ts4, Value: &metricspb.Point_DoubleValue{DoubleValue: 16.0}}, + }, + }, + }, + }, + }, + } + doCompare("scrape4", t, want4, &m4) + + // verify the 4th metricData which reset happens, all cumulative types shall be absent + m5 := mds[4] + // its start timestamp shall be from the 3rd run + ts5 := m5.Metrics[0].Timeseries[0].Points[0].Timestamp + + want5 := &consumerdata.MetricsData{ + Node: td.node, + Resource: td.resource, + Metrics: []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Timestamp: ts5, Value: &metricspb.Point_DoubleValue{DoubleValue: 16.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_requests_total", + Description: "The total number of HTTP requests.", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*metricspb.LabelKey{{Key: "code"}, {Key: "method"}}, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts4, + LabelValues: []*metricspb.LabelValue{ + {Value: "200", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts5, Value: &metricspb.Point_DoubleValue{DoubleValue: 1.0}}, + }, + }, + { + StartTimestamp: ts4, + LabelValues: []*metricspb.LabelValue{ + {Value: "400", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts5, Value: &metricspb.Point_DoubleValue{DoubleValue: 0.0}}, + }, + }, + { + StartTimestamp: ts4, + LabelValues: []*metricspb.LabelValue{ + {Value: "500", HasValue: true}, + {Value: "post", HasValue: true}, + }, + Points: []*metricspb.Point{ + {Timestamp: ts5, Value: &metricspb.Point_DoubleValue{DoubleValue: 2.0}}, + }, + }, + }, + }, + }, + } + doCompare("scrape5", t, want5, &m5) +} + +// target3 for complicated data types, including summaries and histograms. one of the summary and histogram have only +// sum/count, for the summary it's valid, however the histogram one is not, but it shall not cause the scrape to fail +var target3Page1 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 18 + +# A histogram, which has a pretty complex representation in the text format: +# HELP http_request_duration_seconds A histogram of the request duration. +# TYPE http_request_duration_seconds histogram +http_request_duration_seconds_bucket{le="0.2"} 10000 +http_request_duration_seconds_bucket{le="0.5"} 11000 +http_request_duration_seconds_bucket{le="1"} 12001 +http_request_duration_seconds_bucket{le="+Inf"} 13003 +http_request_duration_seconds_sum 50000 +http_request_duration_seconds_count 13003 + +# A corrupted histogram with only sum and count +# HELP corrupted_hist A corrupted_hist. +# TYPE corrupted_hist histogram +corrupted_hist_sum 100 +corrupted_hist_count 10 + +# Finally a summary, which has a complex representation, too: +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{foo="bar" quantile="0.01"} 31 +rpc_duration_seconds{foo="bar" quantile="0.05"} 35 +rpc_duration_seconds{foo="bar" quantile="0.5"} 47 +rpc_duration_seconds{foo="bar" quantile="0.9"} 70 +rpc_duration_seconds{foo="bar" quantile="0.99"} 76 +rpc_duration_seconds_sum{foo="bar"} 8000 +rpc_duration_seconds_count{foo="bar"} 900 +rpc_duration_seconds_sum{foo="no_quantile"} 100 +rpc_duration_seconds_count{foo="no_quantile"} 50 +` + +var target3Page2 = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 16 + +# A histogram, which has a pretty complex representation in the text format: +# HELP http_request_duration_seconds A histogram of the request duration. +# TYPE http_request_duration_seconds histogram +http_request_duration_seconds_bucket{le="0.2"} 11000 +http_request_duration_seconds_bucket{le="0.5"} 12000 +http_request_duration_seconds_bucket{le="1"} 13001 +http_request_duration_seconds_bucket{le="+Inf"} 14003 +http_request_duration_seconds_sum 50100 +http_request_duration_seconds_count 14003 + +# A corrupted histogram with only sum and count +# HELP corrupted_hist A corrupted_hist. +# TYPE corrupted_hist histogram +corrupted_hist_sum 101 +corrupted_hist_count 15 + +# Finally a summary, which has a complex representation, too: +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{foo="bar" quantile="0.01"} 32 +rpc_duration_seconds{foo="bar" quantile="0.05"} 35 +rpc_duration_seconds{foo="bar" quantile="0.5"} 47 +rpc_duration_seconds{foo="bar" quantile="0.9"} 70 +rpc_duration_seconds{foo="bar" quantile="0.99"} 77 +rpc_duration_seconds_sum{foo="bar"} 8100 +rpc_duration_seconds_count{foo="bar"} 950 +rpc_duration_seconds_sum{foo="no_quantile"} 101 +rpc_duration_seconds_count{foo="no_quantile"} 55 +` + +func verifyTarget3(t *testing.T, td *testData, mds []consumerdata.MetricsData) { + verifyNumScrapeResults(t, td, mds) + m1 := mds[0] + // m1 shall only have a gauge + if l := len(m1.Metrics); l != 1 { + t.Errorf("want 1, but got %v\n", l) + } + + // only gauge value is returned from the first scrape + wantG1 := &metricspb.Metric{ + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE}, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Value: &metricspb.Point_DoubleValue{DoubleValue: 18.0}}, + }, + }, + }, + } + gotG1 := m1.Metrics[0] + ts1 := gotG1.Timeseries[0].Points[0].Timestamp + // set this timestamp to wantG1 + wantG1.Timeseries[0].Points[0].Timestamp = ts1 + doCompare("scrape1", t, wantG1, gotG1) + + // verify the 2nd metricData + m2 := mds[1] + ts2 := m2.Metrics[0].Timeseries[0].Points[0].Timestamp + + want2 := &consumerdata.MetricsData{ + Node: td.node, + Resource: td.resource, + Metrics: []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "go_threads", + Description: "Number of OS threads created", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + }, + Timeseries: []*metricspb.TimeSeries{ + { + Points: []*metricspb.Point{ + {Timestamp: ts2, Value: &metricspb.Point_DoubleValue{DoubleValue: 16.0}}, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "http_request_duration_seconds", + Description: "A histogram of the request duration.", + Unit: "s", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + Points: []*metricspb.Point{ + { + Timestamp: ts2, + Value: &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{0.2, 0.5, 1}, + }, + }, + }, + Count: 1000, + Sum: 100, + Buckets: []*metricspb.DistributionValue_Bucket{ + {Count: 1000}, + {Count: 0}, + {Count: 0}, + {Count: 0}, + }, + }, + }, + }, + }, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: "rpc_duration_seconds", + Type: metricspb.MetricDescriptor_SUMMARY, + LabelKeys: []*metricspb.LabelKey{{Key: "foo"}}, + Description: "A summary of the RPC duration in seconds.", + Unit: "s", + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{{Value: "bar", HasValue: true}}, + Points: []*metricspb.Point{ + { + Timestamp: ts2, + Value: &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrappers.DoubleValue{Value: 100}, + Count: &wrappers.Int64Value{Value: 50}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ + { + Percentile: 1, + Value: 32, + }, + { + Percentile: 5, + Value: 35, + }, + { + Percentile: 50, + Value: 47, + }, + { + Percentile: 90, + Value: 70, + }, + { + Percentile: 99, + Value: 77, + }, + }, + }, + }, + }, + }, + }, + }, + { + StartTimestamp: ts1, + LabelValues: []*metricspb.LabelValue{{Value: "no_quantile", HasValue: true}}, + Points: []*metricspb.Point{ + { + Timestamp: ts2, + Value: &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrappers.DoubleValue{Value: 1}, + Count: &wrappers.Int64Value{Value: 5}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: nil, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + doCompare("scrape2", t, want2, &m2) +} + +// TestEndToEnd end to end test executor +func TestEndToEnd(t *testing.T) { + // 1. setup input data + targets := []*testData{ + { + name: "target1", + pages: []mockPrometheusResponse{ + {code: 200, data: target1Page1}, + {code: 500, data: ""}, + {code: 200, data: target1Page2}, + }, + validateFunc: verifyTarget1, + }, + { + name: "target2", + pages: []mockPrometheusResponse{ + {code: 200, data: target2Page1}, + {code: 200, data: target2Page2}, + {code: 500, data: ""}, + {code: 200, data: target2Page3}, + {code: 200, data: target2Page4}, + {code: 500, data: ""}, + {code: 200, data: target2Page5}, + }, + validateFunc: verifyTarget2, + }, + { + name: "target3", + pages: []mockPrometheusResponse{ + {code: 200, data: target3Page1}, + {code: 200, data: target3Page2}, + }, + validateFunc: verifyTarget3, + }, + } + + testEndToEnd(t, targets, false) +} + +var startTimeMetricPage = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 19 +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 100 +http_requests_total{method="post",code="400"} 5 +# HELP http_request_duration_seconds A histogram of the request duration. +# TYPE http_request_duration_seconds histogram +http_request_duration_seconds_bucket{le="0.05"} 1000 +http_request_duration_seconds_bucket{le="0.5"} 1500 +http_request_duration_seconds_bucket{le="1"} 2000 +http_request_duration_seconds_bucket{le="+Inf"} 2500 +http_request_duration_seconds_sum 5000 +http_request_duration_seconds_count 2500 +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{quantile="0.01"} 1 +rpc_duration_seconds{quantile="0.9"} 5 +rpc_duration_seconds{quantile="0.99"} 8 +rpc_duration_seconds_sum 5000 +rpc_duration_seconds_count 1000 +# HELP process_start_time_seconds Start time of the process since unix epoch in seconds. +# TYPE process_start_time_seconds gauge +process_start_time_seconds 400.8 +` + +var startTimeMetricPageStartTimestamp = ×tamppb.Timestamp{Seconds: 400, Nanos: 800000000} + +const numStartTimeMetricPageTimeseries = 6 + +func verifyStartTimeMetricPage(t *testing.T, _ *testData, mds []consumerdata.MetricsData) { + numTimeseries := 0 + for _, cmd := range mds { + for _, metric := range cmd.Metrics { + timestamp := startTimeMetricPageStartTimestamp + switch metric.GetMetricDescriptor().GetType() { + case metricspb.MetricDescriptor_GAUGE_DOUBLE, metricspb.MetricDescriptor_GAUGE_DISTRIBUTION: + timestamp = nil + } + for _, ts := range metric.GetTimeseries() { + assert.Equal(t, timestamp, ts.GetStartTimestamp()) + numTimeseries++ + } + } + } + assert.Equal(t, numStartTimeMetricPageTimeseries, numTimeseries) +} + +// TestStartTimeMetric validates that timeseries have start time set to 'process_start_time_seconds' +func TestStartTimeMetric(t *testing.T) { + targets := []*testData{ + { + name: "target1", + pages: []mockPrometheusResponse{ + {code: 200, data: startTimeMetricPage}, + }, + validateFunc: verifyStartTimeMetricPage, + }, + } + testEndToEnd(t, targets, true) +} + +func testEndToEnd(t *testing.T, targets []*testData, useStartTimeMetric bool) { + // 1. setup mock server + mp, cfg, err := setupMockPrometheus(targets...) + require.Nilf(t, err, "Failed to create Promtheus config: %v", err) + defer mp.Close() + + cms := new(consumertest.MetricsSink) + rcvr := newPrometheusReceiver(logger, &Config{PrometheusConfig: cfg, UseStartTimeMetric: useStartTimeMetric}, cms) + + require.NoError(t, rcvr.Start(context.Background(), componenttest.NewNopHost()), "Failed to invoke Start: %v", err) + defer rcvr.Shutdown(context.Background()) + + // wait for all provided data to be scraped + mp.wg.Wait() + metrics := cms.AllMetrics() + + // split and store results by target name + results := make(map[string][]consumerdata.MetricsData) + for _, m := range metrics { + ocmds := internaldata.MetricsToOC(m) + for _, ocmd := range ocmds { + result, ok := results[ocmd.Node.ServiceInfo.Name] + if !ok { + result = make([]consumerdata.MetricsData, 0) + } + results[ocmd.Node.ServiceInfo.Name] = append(result, ocmd) + } + } + + lres, lep := len(results), len(mp.endpoints) + assert.Equalf(t, lep, lres, "want %d targets, but got %v\n", lep, lres) + + // loop to validate outputs for each targets + for _, target := range targets { + target.validateFunc(t, target, results[target.name]) + } +} + +var startTimeMetricRegexPage = ` +# HELP go_threads Number of OS threads created +# TYPE go_threads gauge +go_threads 19 +# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{method="post",code="200"} 100 +http_requests_total{method="post",code="400"} 5 +# HELP http_request_duration_seconds A histogram of the request duration. +# TYPE http_request_duration_seconds histogram +http_request_duration_seconds_bucket{le="0.05"} 1000 +http_request_duration_seconds_bucket{le="0.5"} 1500 +http_request_duration_seconds_bucket{le="1"} 2000 +http_request_duration_seconds_bucket{le="+Inf"} 2500 +http_request_duration_seconds_sum 5000 +http_request_duration_seconds_count 2500 +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{quantile="0.01"} 1 +rpc_duration_seconds{quantile="0.9"} 5 +rpc_duration_seconds{quantile="0.99"} 8 +rpc_duration_seconds_sum 5000 +rpc_duration_seconds_count 1000 +# HELP example_process_start_time_seconds Start time of the process since unix epoch in seconds. +# TYPE example_process_start_time_seconds gauge +example_process_start_time_seconds 400.8 +` + +// TestStartTimeMetricRegex validates that timeseries have start time regex set to 'process_start_time_seconds' +func TestStartTimeMetricRegex(t *testing.T) { + targets := []*testData{ + { + name: "target1", + pages: []mockPrometheusResponse{ + {code: 200, data: startTimeMetricRegexPage}, + }, + validateFunc: verifyStartTimeMetricPage, + }, + { + name: "target2", + pages: []mockPrometheusResponse{ + {code: 200, data: startTimeMetricPage}, + }, + validateFunc: verifyStartTimeMetricPage, + }, + } + testEndToEndRegex(t, targets, true, "^(.+_)*process_start_time_seconds$") +} + +func testEndToEndRegex(t *testing.T, targets []*testData, useStartTimeMetric bool, startTimeMetricRegex string) { + // 1. setup mock server + mp, cfg, err := setupMockPrometheus(targets...) + require.Nilf(t, err, "Failed to create Promtheus config: %v", err) + defer mp.Close() + + cms := new(consumertest.MetricsSink) + rcvr := newPrometheusReceiver(logger, &Config{PrometheusConfig: cfg, UseStartTimeMetric: useStartTimeMetric, StartTimeMetricRegex: startTimeMetricRegex}, cms) + + require.NoError(t, rcvr.Start(context.Background(), componenttest.NewNopHost()), "Failed to invoke Start: %v", err) + defer rcvr.Shutdown(context.Background()) + + // wait for all provided data to be scraped + mp.wg.Wait() + metrics := cms.AllMetrics() + + // split and store results by target name + results := make(map[string][]consumerdata.MetricsData) + for _, m := range metrics { + ocmds := internaldata.MetricsToOC(m) + for _, ocmd := range ocmds { + result, ok := results[ocmd.Node.ServiceInfo.Name] + if !ok { + result = make([]consumerdata.MetricsData, 0) + } + results[ocmd.Node.ServiceInfo.Name] = append(result, ocmd) + } + } + + lres, lep := len(results), len(mp.endpoints) + assert.Equalf(t, lep, lres, "want %d targets, but got %v\n", lep, lres) + + // loop to validate outputs for each targets + for _, target := range targets { + target.validateFunc(t, target, results[target.name]) + } +} diff --git a/internal/otel_collector/receiver/prometheusreceiver/scrapeloop-flowchart.png b/internal/otel_collector/receiver/prometheusreceiver/scrapeloop-flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..5853a9df927b92918c2a566081509da26883b0ac GIT binary patch literal 43026 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 UEe*L0gbcQSx81Iyoyhb552Ws7(EtDd literal 0 HcmV?d00001 diff --git a/internal/otel_collector/receiver/prometheusreceiver/testdata/config.yaml b/internal/otel_collector/receiver/prometheusreceiver/testdata/config.yaml new file mode 100644 index 00000000000..32513a9ad79 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/testdata/config.yaml @@ -0,0 +1,24 @@ +receivers: + prometheus: + prometheus/customname: + buffer_period: 234 + buffer_count: 45 + use_start_time_metric: true + start_time_metric_regex: '^(.+_)*process_start_time_seconds$' + config: + scrape_configs: + - job_name: 'demo' + scrape_interval: 5s + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [prometheus] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/prometheusreceiver/testdata/config_env.yaml b/internal/otel_collector/receiver/prometheusreceiver/testdata/config_env.yaml new file mode 100644 index 00000000000..0ca8640781f --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/testdata/config_env.yaml @@ -0,0 +1,19 @@ +receivers: + prometheus: + config: + scrape_configs: + - job_name: ${JOBNAME} + scrape_interval: 5s + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [prometheus] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/prometheusreceiver/testdata/config_k8s.yaml b/internal/otel_collector/receiver/prometheusreceiver/testdata/config_k8s.yaml new file mode 100644 index 00000000000..c73ed6eee55 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/testdata/config_k8s.yaml @@ -0,0 +1,42 @@ +receivers: + prometheus: + config: + scrape_configs: + - job_name: apps + kubernetes_sd_configs: + - role: pod + selectors: + - role: pod + # only scrape data from pods running on the same node as collector + field: "spec.nodeName=$NODE_NAME" + relabel_configs: + # scrape pods annotated with "prometheus.io/scrape: true" + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] + regex: "true" + action: keep + # read the port from "prometheus.io/port: " annotation and update scraping address accordingly + - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] + action: replace + target_label: __address__ + regex: ([^:]+)(?::\d+)?;(\d+) + # escaped $1:$2 + replacement: $$1:$$2 + - source_labels: [__meta_kubernetes_namespace] + action: replace + target_label: kubernetes_namespace + - source_labels: [__meta_kubernetes_pod_name] + action: replace + target_label: kubernetes_pod_name + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [prometheus] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/prometheusreceiver/testdata/config_sd.yaml b/internal/otel_collector/receiver/prometheusreceiver/testdata/config_sd.yaml new file mode 100644 index 00000000000..1179b1b6122 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/testdata/config_sd.yaml @@ -0,0 +1,85 @@ +receivers: + prometheus: + config: + scrape_configs: + - job_name: file + file_sd_configs: + - files: + - 'dummy.json' + - job_name: k8s + kubernetes_sd_configs: + - role: node + - job_name: ec2 + ec2_sd_configs: + - region: us-west-2 + - job_name: gce + gce_sd_configs: + - project: my-project + zone: my-zone + - job_name: dns + dns_sd_configs: + - names: + - name1 + - job_name: openstack + openstack_sd_configs: + - role: hypervisor + region: region + - job_name: hetzner + hetzner_sd_configs: + - role: robot + - job_name: marathon + marathon_sd_configs: + - servers: + - server1 + - job_name: nerve + nerve_sd_configs: + - servers: + - server1 + paths: + - /path1 + - job_name: serverset + serverset_sd_configs: + - servers: + - server1 + paths: + - /path1 + - job_name: triton + triton_sd_configs: + - account: account + dns_suffix: suffix + endpoint: endpoint + - job_name: eureka + eureka_sd_configs: + - server: http://server1 + - job_name: azure + azure_sd_configs: + - subscription_id: subscription + tenant_id: tenant + client_id: client + client_secret: secret + - job_name: consul + consul_sd_configs: + - server: server1 + - job_name: digitalocean + digitalocean_sd_configs: + - basic_auth: + username: username + password: password + - job_name: dockerswarm_sd_config + dockerswarm_sd_configs: + - host: host + role: nodes + + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [prometheus] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-prometheus-section.yaml b/internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-prometheus-section.yaml new file mode 100644 index 00000000000..3f8df1acedd --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-prometheus-section.yaml @@ -0,0 +1,20 @@ +receivers: + prometheus: + config: + use_start_time_metric: true + scrape_configs: + - job_name: 'demo' + scrape_interval: 5s + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [prometheus] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-section.yaml b/internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-section.yaml new file mode 100644 index 00000000000..9dfe6cebab3 --- /dev/null +++ b/internal/otel_collector/receiver/prometheusreceiver/testdata/invalid-config-section.yaml @@ -0,0 +1,20 @@ +receivers: + prometheus: + unknow_section: 1 + config: + scrape_configs: + - job_name: 'demo' + scrape_interval: 5s + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [prometheus] + processors: [exampleprocessor] + exporters: [exampleexporter] diff --git a/internal/otel_collector/receiver/receiverhelper/factory.go b/internal/otel_collector/receiver/receiverhelper/factory.go new file mode 100644 index 00000000000..206b2777275 --- /dev/null +++ b/internal/otel_collector/receiver/receiverhelper/factory.go @@ -0,0 +1,157 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package receiverhelper + +import ( + "context" + + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +// FactoryOption apply changes to ReceiverOptions. +type FactoryOption func(o *factory) + +// WithCustomUnmarshaler implements component.ConfigUnmarshaler. +func WithCustomUnmarshaler(customUnmarshaler component.CustomUnmarshaler) FactoryOption { + return func(o *factory) { + o.customUnmarshaler = customUnmarshaler + } +} + +// WithTraces overrides the default "error not supported" implementation for CreateTraceReceiver. +func WithTraces(createTraceReceiver CreateTraceReceiver) FactoryOption { + return func(o *factory) { + o.createTraceReceiver = createTraceReceiver + } +} + +// WithMetrics overrides the default "error not supported" implementation for CreateMetricsReceiver. +func WithMetrics(createMetricsReceiver CreateMetricsReceiver) FactoryOption { + return func(o *factory) { + o.createMetricsReceiver = createMetricsReceiver + } +} + +// WithLogs overrides the default "error not supported" implementation for CreateLogsReceiver. +func WithLogs(createLogsReceiver CreateLogsReceiver) FactoryOption { + return func(o *factory) { + o.createLogsReceiver = createLogsReceiver + } +} + +// CreateDefaultConfig is the equivalent of component.ReceiverFactory.CreateDefaultConfig() +type CreateDefaultConfig func() configmodels.Receiver + +// CreateTraceReceiver is the equivalent of component.ReceiverFactory.CreateTracesReceiver() +type CreateTraceReceiver func(context.Context, component.ReceiverCreateParams, configmodels.Receiver, consumer.TracesConsumer) (component.TracesReceiver, error) + +// CreateMetricsReceiver is the equivalent of component.ReceiverFactory.CreateMetricsReceiver() +type CreateMetricsReceiver func(context.Context, component.ReceiverCreateParams, configmodels.Receiver, consumer.MetricsConsumer) (component.MetricsReceiver, error) + +// CreateLogsReceiver is the equivalent of component.ReceiverFactory.CreateLogsReceiver() +type CreateLogsReceiver func(context.Context, component.ReceiverCreateParams, configmodels.Receiver, consumer.LogsConsumer) (component.LogsReceiver, error) + +type factory struct { + cfgType configmodels.Type + customUnmarshaler component.CustomUnmarshaler + createDefaultConfig CreateDefaultConfig + createTraceReceiver CreateTraceReceiver + createMetricsReceiver CreateMetricsReceiver + createLogsReceiver CreateLogsReceiver +} + +// NewFactory returns a component.ReceiverFactory. +func NewFactory( + cfgType configmodels.Type, + createDefaultConfig CreateDefaultConfig, + options ...FactoryOption) component.ReceiverFactory { + f := &factory{ + cfgType: cfgType, + createDefaultConfig: createDefaultConfig, + } + for _, opt := range options { + opt(f) + } + var ret component.ReceiverFactory + if f.customUnmarshaler != nil { + ret = &factoryWithUnmarshaler{f} + } else { + ret = f + } + return ret +} + +// Type gets the type of the Receiver config created by this factory. +func (f *factory) Type() configmodels.Type { + return f.cfgType +} + +// CreateDefaultConfig creates the default configuration for receiver. +func (f *factory) CreateDefaultConfig() configmodels.Receiver { + return f.createDefaultConfig() +} + +// CreateTraceReceiver creates a component.TracesReceiver based on this config. +func (f *factory) CreateTracesReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer) (component.TracesReceiver, error) { + if f.createTraceReceiver != nil { + return f.createTraceReceiver(ctx, params, cfg, nextConsumer) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateMetricsReceiver creates a consumer.MetricsConsumer based on this config. +func (f *factory) CreateMetricsReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.MetricsConsumer) (component.MetricsReceiver, error) { + if f.createMetricsReceiver != nil { + return f.createMetricsReceiver(ctx, params, cfg, nextConsumer) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +// CreateLogsReceiver creates a metrics processor based on this config. +func (f *factory) CreateLogsReceiver( + ctx context.Context, + params component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.LogsConsumer, +) (component.LogsReceiver, error) { + if f.createLogsReceiver != nil { + return f.createLogsReceiver(ctx, params, cfg, nextConsumer) + } + return nil, configerror.ErrDataTypeIsNotSupported +} + +var _ component.ConfigUnmarshaler = (*factoryWithUnmarshaler)(nil) + +type factoryWithUnmarshaler struct { + *factory +} + +// Unmarshal un-marshals the config using the provided custom unmarshaler. +func (f *factoryWithUnmarshaler) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error { + return f.customUnmarshaler(componentViperSection, intoCfg) +} diff --git a/internal/otel_collector/receiver/receiverhelper/factory_test.go b/internal/otel_collector/receiver/receiverhelper/factory_test.go new file mode 100644 index 00000000000..a215cd6d060 --- /dev/null +++ b/internal/otel_collector/receiver/receiverhelper/factory_test.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package receiverhelper + +import ( + "context" + "errors" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" +) + +const typeStr = "test" + +var defaultCfg = &configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, +} + +func TestNewFactory(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + _, ok := factory.(component.ConfigUnmarshaler) + assert.False(t, ok) + _, err := factory.CreateTracesReceiver(context.Background(), component.ReceiverCreateParams{}, defaultCfg, nil) + assert.Error(t, err) + _, err = factory.CreateMetricsReceiver(context.Background(), component.ReceiverCreateParams{}, defaultCfg, nil) + assert.Error(t, err) + _, err = factory.CreateLogsReceiver(context.Background(), component.ReceiverCreateParams{}, defaultCfg, nil) + assert.Error(t, err) +} + +func TestNewFactory_WithConstructors(t *testing.T) { + factory := NewFactory( + typeStr, + defaultConfig, + WithTraces(createTraceReceiver), + WithMetrics(createMetricsReceiver), + WithLogs(createLogsReceiver), + WithCustomUnmarshaler(customUnmarshaler)) + assert.EqualValues(t, typeStr, factory.Type()) + assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig()) + + fu, ok := factory.(component.ConfigUnmarshaler) + assert.True(t, ok) + assert.Equal(t, errors.New("my error"), fu.Unmarshal(nil, nil)) + + _, err := factory.CreateTracesReceiver(context.Background(), component.ReceiverCreateParams{}, defaultCfg, nil) + assert.NoError(t, err) + + _, err = factory.CreateMetricsReceiver(context.Background(), component.ReceiverCreateParams{}, defaultCfg, nil) + assert.NoError(t, err) + + _, err = factory.CreateLogsReceiver(context.Background(), component.ReceiverCreateParams{}, defaultCfg, nil) + assert.NoError(t, err) +} + +func defaultConfig() configmodels.Receiver { + return defaultCfg +} + +func createTraceReceiver(context.Context, component.ReceiverCreateParams, configmodels.Receiver, consumer.TracesConsumer) (component.TracesReceiver, error) { + return nil, nil +} + +func createMetricsReceiver(context.Context, component.ReceiverCreateParams, configmodels.Receiver, consumer.MetricsConsumer) (component.MetricsReceiver, error) { + return nil, nil +} + +func createLogsReceiver(context.Context, component.ReceiverCreateParams, configmodels.Receiver, consumer.LogsConsumer) (component.LogsReceiver, error) { + return nil, nil +} + +func customUnmarshaler(*viper.Viper, interface{}) error { + return errors.New("my error") +} diff --git a/internal/otel_collector/receiver/scraperhelper/errors.go b/internal/otel_collector/receiver/scraperhelper/errors.go new file mode 100644 index 00000000000..6134aebd17f --- /dev/null +++ b/internal/otel_collector/receiver/scraperhelper/errors.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scraperhelper + +import ( + "errors" + "fmt" + "strings" + + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer/consumererror" +) + +// CombineScrapeErrors converts a list of errors into one error. +func CombineScrapeErrors(errs []error) error { + partialScrapeErr := false + for _, err := range errs { + var partialError consumererror.PartialScrapeError + if errors.As(err, &partialError) { + partialScrapeErr = true + break + } + } + + if !partialScrapeErr { + return componenterror.CombineErrors(errs) + } + + errMsgs := make([]string, 0, len(errs)) + failedScrapeCount := 0 + for _, err := range errs { + if partialError, isPartial := err.(consumererror.PartialScrapeError); isPartial { + failedScrapeCount += partialError.Failed + } + + errMsgs = append(errMsgs, err.Error()) + } + + var err error + if len(errs) == 1 { + err = errs[0] + } else { + err = fmt.Errorf("[%s]", strings.Join(errMsgs, "; ")) + } + + return consumererror.NewPartialScrapeError(err, failedScrapeCount) +} diff --git a/internal/otel_collector/receiver/scraperhelper/errors_test.go b/internal/otel_collector/receiver/scraperhelper/errors_test.go new file mode 100644 index 00000000000..e5def5a7c44 --- /dev/null +++ b/internal/otel_collector/receiver/scraperhelper/errors_test.go @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scraperhelper + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/consumererror" +) + +func TestCombineScrapeErrors(t *testing.T) { + testCases := []struct { + errors []error + expected string + expectNil bool + expectedPartialScrapeErr bool + expectedFailedScrapeCount int + }{ + { + errors: []error{}, + expectNil: true, + }, + { + errors: []error{ + fmt.Errorf("foo"), + }, + expected: "foo", + }, + { + errors: []error{ + fmt.Errorf("foo"), + fmt.Errorf("bar"), + }, + expected: "[foo; bar]", + }, + { + errors: []error{ + fmt.Errorf("foo"), + fmt.Errorf("bar"), + consumererror.NewPartialScrapeError(fmt.Errorf("partial"), 0)}, + expected: "[foo; bar; partial]", + expectedPartialScrapeErr: true, + expectedFailedScrapeCount: 0, + }, + { + errors: []error{ + fmt.Errorf("foo"), + fmt.Errorf("bar"), + consumererror.NewPartialScrapeError(fmt.Errorf("partial 1"), 2), + consumererror.NewPartialScrapeError(fmt.Errorf("partial 2"), 3)}, + expected: "[foo; bar; partial 1; partial 2]", + expectedPartialScrapeErr: true, + expectedFailedScrapeCount: 5, + }, + } + + for _, tc := range testCases { + got := CombineScrapeErrors(tc.errors) + + if tc.expectNil { + assert.NoError(t, got, tc.expected) + } else { + assert.EqualError(t, got, tc.expected) + } + + partialErr, isPartial := got.(consumererror.PartialScrapeError) + assert.Equal(t, tc.expectedPartialScrapeErr, isPartial) + + if tc.expectedPartialScrapeErr && isPartial { + assert.Equal(t, tc.expectedFailedScrapeCount, partialErr.Failed) + } + } +} diff --git a/internal/otel_collector/receiver/scraperhelper/scraper.go b/internal/otel_collector/receiver/scraperhelper/scraper.go new file mode 100644 index 00000000000..06e0aa2b52d --- /dev/null +++ b/internal/otel_collector/receiver/scraperhelper/scraper.go @@ -0,0 +1,168 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scraperhelper + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenthelper" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" +) + +// Scrape metrics. +type ScrapeMetrics func(context.Context) (pdata.MetricSlice, error) + +// Scrape resource metrics. +type ScrapeResourceMetrics func(context.Context) (pdata.ResourceMetricsSlice, error) + +// ScraperOption apply changes to internal options. +type ScraperOption func(*componenthelper.ComponentSettings) + +type BaseScraper interface { + component.Component + + // Name returns the scraper name + Name() string +} + +// MetricsScraper is an interface for scrapers that scrape metrics. +type MetricsScraper interface { + BaseScraper + Scrape(context.Context, string) (pdata.MetricSlice, error) +} + +// ResourceMetricsScraper is an interface for scrapers that scrape resource metrics. +type ResourceMetricsScraper interface { + BaseScraper + Scrape(context.Context, string) (pdata.ResourceMetricsSlice, error) +} + +var _ BaseScraper = (*baseScraper)(nil) + +type baseScraper struct { + component.Component + name string +} + +func (b baseScraper) Name() string { + return b.name +} + +// WithStart sets the function that will be called on startup. +func WithStart(start componenthelper.Start) ScraperOption { + return func(s *componenthelper.ComponentSettings) { + s.Start = start + } +} + +// WithShutdown sets the function that will be called on shutdown. +func WithShutdown(shutdown componenthelper.Shutdown) ScraperOption { + return func(s *componenthelper.ComponentSettings) { + s.Shutdown = shutdown + } +} + +type metricsScraper struct { + baseScraper + ScrapeMetrics +} + +var _ MetricsScraper = (*metricsScraper)(nil) + +// NewMetricsScraper creates a Scraper that calls Scrape at the specified +// collection interval, reports observability information, and passes the +// scraped metrics to the next consumer. +func NewMetricsScraper( + name string, + scrape ScrapeMetrics, + options ...ScraperOption, +) MetricsScraper { + set := componenthelper.DefaultComponentSettings() + for _, op := range options { + op(set) + } + + ms := &metricsScraper{ + baseScraper: baseScraper{ + Component: componenthelper.NewComponent(set), + name: name, + }, + ScrapeMetrics: scrape, + } + + return ms +} + +func (ms metricsScraper) Scrape(ctx context.Context, receiverName string) (pdata.MetricSlice, error) { + ctx = obsreport.ScraperContext(ctx, receiverName, ms.Name()) + ctx = obsreport.StartMetricsScrapeOp(ctx, receiverName, ms.Name()) + metrics, err := ms.ScrapeMetrics(ctx) + obsreport.EndMetricsScrapeOp(ctx, metrics.Len(), err) + return metrics, err +} + +type resourceMetricsScraper struct { + baseScraper + ScrapeResourceMetrics +} + +var _ ResourceMetricsScraper = (*resourceMetricsScraper)(nil) + +// NewResourceMetricsScraper creates a Scraper that calls Scrape at the +// specified collection interval, reports observability information, and +// passes the scraped resource metrics to the next consumer. +func NewResourceMetricsScraper( + name string, + scrape ScrapeResourceMetrics, + options ...ScraperOption, +) ResourceMetricsScraper { + set := componenthelper.DefaultComponentSettings() + for _, op := range options { + op(set) + } + + rms := &resourceMetricsScraper{ + baseScraper: baseScraper{ + Component: componenthelper.NewComponent(set), + name: name, + }, + ScrapeResourceMetrics: scrape, + } + + return rms +} + +func (rms resourceMetricsScraper) Scrape(ctx context.Context, receiverName string) (pdata.ResourceMetricsSlice, error) { + ctx = obsreport.ScraperContext(ctx, receiverName, rms.Name()) + ctx = obsreport.StartMetricsScrapeOp(ctx, receiverName, rms.Name()) + resourceMetrics, err := rms.ScrapeResourceMetrics(ctx) + obsreport.EndMetricsScrapeOp(ctx, metricCount(resourceMetrics), err) + return resourceMetrics, err +} + +func metricCount(resourceMetrics pdata.ResourceMetricsSlice) int { + count := 0 + + for i := 0; i < resourceMetrics.Len(); i++ { + ilm := resourceMetrics.At(i).InstrumentationLibraryMetrics() + for j := 0; j < ilm.Len(); j++ { + count += ilm.At(j).Metrics().Len() + } + } + + return count +} diff --git a/internal/otel_collector/receiver/scraperhelper/scrapercontroller.go b/internal/otel_collector/receiver/scraperhelper/scrapercontroller.go new file mode 100644 index 00000000000..e829f6ca204 --- /dev/null +++ b/internal/otel_collector/receiver/scraperhelper/scrapercontroller.go @@ -0,0 +1,276 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scraperhelper + +import ( + "context" + "errors" + "time" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" +) + +// ScraperControllerSettings defines common settings for a scraper controller +// configuration. Scraper controller receivers can embed this struct, instead +// of configmodels.ReceiverSettings, and extend it with more fields if needed. +type ScraperControllerSettings struct { + configmodels.ReceiverSettings `mapstructure:"squash"` + CollectionInterval time.Duration `mapstructure:"collection_interval"` +} + +// DefaultScraperControllerSettings returns default scraper controller +// settings with a collection interval of one minute. +func DefaultScraperControllerSettings(cfgType configmodels.Type) ScraperControllerSettings { + return ScraperControllerSettings{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: string(cfgType), + TypeVal: cfgType, + }, + CollectionInterval: time.Minute, + } +} + +// ScraperControllerOption apply changes to internal options. +type ScraperControllerOption func(*controller) + +// AddMetricsScraper configures the provided scrape function to be called +// with the specified options, and at the specified collection interval. +// +// Observability information will be reported, and the scraped metrics +// will be passed to the next consumer. +func AddMetricsScraper(scraper MetricsScraper) ScraperControllerOption { + return func(o *controller) { + o.metricsScrapers.scrapers = append(o.metricsScrapers.scrapers, scraper) + } +} + +// AddResourceMetricsScraper configures the provided scrape function to +// be called with the specified options, and at the specified collection +// interval. +// +// Observability information will be reported, and the scraped resource +// metrics will be passed to the next consumer. +func AddResourceMetricsScraper(scraper ResourceMetricsScraper) ScraperControllerOption { + return func(o *controller) { + o.resourceMetricScrapers = append(o.resourceMetricScrapers, scraper) + } +} + +// WithTickerChannel allows you to override the scraper controllers ticker +// channel to specify when scrape is called. This is only expected to be +// used by tests. +func WithTickerChannel(tickerCh <-chan time.Time) ScraperControllerOption { + return func(o *controller) { + o.tickerCh = tickerCh + } +} + +type controller struct { + name string + logger *zap.Logger + collectionInterval time.Duration + nextConsumer consumer.MetricsConsumer + + metricsScrapers *multiMetricScraper + resourceMetricScrapers []ResourceMetricsScraper + + tickerCh <-chan time.Time + + initialized bool + done chan struct{} + terminated chan struct{} +} + +// NewScraperControllerReceiver creates a Receiver with the configured options, that can control multiple scrapers. +func NewScraperControllerReceiver( + cfg *ScraperControllerSettings, + logger *zap.Logger, + nextConsumer consumer.MetricsConsumer, + options ...ScraperControllerOption, +) (component.Receiver, error) { + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + if cfg.CollectionInterval <= 0 { + return nil, errors.New("collection_interval must be a positive duration") + } + + sc := &controller{ + name: cfg.Name(), + logger: logger, + collectionInterval: cfg.CollectionInterval, + nextConsumer: nextConsumer, + metricsScrapers: &multiMetricScraper{}, + done: make(chan struct{}), + terminated: make(chan struct{}), + } + + for _, op := range options { + op(sc) + } + + if len(sc.metricsScrapers.scrapers) > 0 { + sc.resourceMetricScrapers = append(sc.resourceMetricScrapers, sc.metricsScrapers) + } + + return sc, nil +} + +// Start the receiver, invoked during service start. +func (sc *controller) Start(ctx context.Context, host component.Host) error { + for _, scraper := range sc.resourceMetricScrapers { + if err := scraper.Start(ctx, host); err != nil { + return err + } + } + + sc.initialized = true + sc.startScraping() + return nil +} + +// Shutdown the receiver, invoked during service shutdown. +func (sc *controller) Shutdown(ctx context.Context) error { + sc.stopScraping() + + // wait until scraping ticker has terminated + if sc.initialized { + <-sc.terminated + } + + var errs []error + for _, scraper := range sc.resourceMetricScrapers { + if err := scraper.Shutdown(ctx); err != nil { + errs = append(errs, err) + } + } + return componenterror.CombineErrors(errs) +} + +// startScraping initiates a ticker that calls Scrape based on the configured +// collection interval. +func (sc *controller) startScraping() { + go func() { + if sc.tickerCh == nil { + ticker := time.NewTicker(sc.collectionInterval) + defer ticker.Stop() + + sc.tickerCh = ticker.C + } + + for { + select { + case <-sc.tickerCh: + sc.scrapeMetricsAndReport(context.Background()) + case <-sc.done: + sc.terminated <- struct{}{} + return + } + } + }() +} + +// scrapeMetricsAndReport calls the Scrape function for each of the configured +// Scrapers, records observability information, and passes the scraped metrics +// to the next component. +func (sc *controller) scrapeMetricsAndReport(ctx context.Context) { + ctx = obsreport.ReceiverContext(ctx, sc.name, "") + + metrics := pdata.NewMetrics() + + for _, rms := range sc.resourceMetricScrapers { + resourceMetrics, err := rms.Scrape(ctx, sc.name) + if err != nil { + sc.logger.Error("Error scraping metrics", zap.Error(err)) + + if !consumererror.IsPartialScrapeError(err) { + continue + } + } + resourceMetrics.MoveAndAppendTo(metrics.ResourceMetrics()) + } + + _, dataPointCount := metrics.MetricAndDataPointCount() + + ctx = obsreport.StartMetricsReceiveOp(ctx, sc.name, "") + err := sc.nextConsumer.ConsumeMetrics(ctx, metrics) + obsreport.EndMetricsReceiveOp(ctx, "", dataPointCount, err) +} + +// stopScraping stops the ticker +func (sc *controller) stopScraping() { + close(sc.done) +} + +var _ ResourceMetricsScraper = (*multiMetricScraper)(nil) + +type multiMetricScraper struct { + scrapers []MetricsScraper +} + +func (mms *multiMetricScraper) Name() string { + return "" +} + +func (mms *multiMetricScraper) Start(ctx context.Context, host component.Host) error { + for _, scraper := range mms.scrapers { + if err := scraper.Start(ctx, host); err != nil { + return err + } + } + return nil +} + +func (mms *multiMetricScraper) Shutdown(ctx context.Context) error { + var errs []error + for _, scraper := range mms.scrapers { + if err := scraper.Shutdown(ctx); err != nil { + errs = append(errs, err) + } + } + return componenterror.CombineErrors(errs) +} + +func (mms *multiMetricScraper) Scrape(ctx context.Context, receiverName string) (pdata.ResourceMetricsSlice, error) { + rms := pdata.NewResourceMetricsSlice() + rms.Resize(1) + rm := rms.At(0) + ilms := rm.InstrumentationLibraryMetrics() + ilms.Resize(1) + ilm := ilms.At(0) + + var errs []error + for _, scraper := range mms.scrapers { + metrics, err := scraper.Scrape(ctx, receiverName) + if err != nil { + errs = append(errs, err) + if !consumererror.IsPartialScrapeError(err) { + continue + } + } + + metrics.MoveAndAppendTo(ilm.Metrics()) + } + return rms, CombineScrapeErrors(errs) +} diff --git a/internal/otel_collector/receiver/scraperhelper/scrapercontroller_test.go b/internal/otel_collector/receiver/scraperhelper/scrapercontroller_test.go new file mode 100644 index 00000000000..6218e1f6c4c --- /dev/null +++ b/internal/otel_collector/receiver/scraperhelper/scrapercontroller_test.go @@ -0,0 +1,477 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scraperhelper + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/trace" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport/obsreporttest" +) + +type testInitialize struct { + ch chan bool + err error +} + +func (ts *testInitialize) start(context.Context, component.Host) error { + ts.ch <- true + return ts.err +} + +type testClose struct { + ch chan bool + err error +} + +func (ts *testClose) shutdown(context.Context) error { + ts.ch <- true + return ts.err +} + +type testScrapeMetrics struct { + ch chan int + timesScrapeCalled int + err error +} + +func (ts *testScrapeMetrics) scrape(_ context.Context) (pdata.MetricSlice, error) { + ts.timesScrapeCalled++ + ts.ch <- ts.timesScrapeCalled + + if ts.err != nil { + return pdata.NewMetricSlice(), ts.err + } + + return singleMetric(), nil +} + +type testScrapeResourceMetrics struct { + ch chan int + timesScrapeCalled int + err error +} + +func (ts *testScrapeResourceMetrics) scrape(_ context.Context) (pdata.ResourceMetricsSlice, error) { + ts.timesScrapeCalled++ + ts.ch <- ts.timesScrapeCalled + + if ts.err != nil { + return pdata.NewResourceMetricsSlice(), ts.err + } + + return singleResourceMetric(), nil +} + +type metricsTestCase struct { + name string + + scrapers int + resourceScrapers int + scraperControllerSettings *ScraperControllerSettings + nilNextConsumer bool + scrapeErr error + expectedNewErr string + expectScraped bool + + initialize bool + close bool + initializeErr error + closeErr error +} + +func TestScrapeController(t *testing.T) { + testCases := []metricsTestCase{ + { + name: "NoScrapers", + }, + { + name: "AddMetricsScrapersWithCollectionInterval", + scrapers: 2, + expectScraped: true, + }, + { + name: "AddMetricsScrapers_NilNextConsumerError", + scrapers: 2, + nilNextConsumer: true, + expectedNewErr: "nil nextConsumer", + }, + { + name: "AddMetricsScrapersWithCollectionInterval_InvalidCollectionIntervalError", + scrapers: 2, + scraperControllerSettings: &ScraperControllerSettings{CollectionInterval: -time.Millisecond}, + expectedNewErr: "collection_interval must be a positive duration", + }, + { + name: "AddMetricsScrapers_ScrapeError", + scrapers: 2, + scrapeErr: errors.New("err1"), + }, + { + name: "AddMetricsScrapersWithInitializeAndClose", + scrapers: 2, + initialize: true, + close: true, + }, + { + name: "AddMetricsScrapersWithInitializeAndCloseErrors", + scrapers: 2, + initialize: true, + close: true, + initializeErr: errors.New("err1"), + closeErr: errors.New("err2"), + }, + { + name: "AddResourceMetricsScrapersWithCollectionInterval", + resourceScrapers: 2, + expectScraped: true, + }, + { + name: "AddResourceMetricsScrapers_NewError", + resourceScrapers: 2, + nilNextConsumer: true, + expectedNewErr: "nil nextConsumer", + }, + { + name: "AddResourceMetricsScrapers_ScrapeError", + resourceScrapers: 2, + scrapeErr: errors.New("err1"), + }, + { + name: "AddResourceMetricsScrapersWithInitializeAndClose", + resourceScrapers: 2, + initialize: true, + close: true, + }, + { + name: "AddResourceMetricsScrapersWithInitializeAndCloseErrors", + resourceScrapers: 2, + initialize: true, + close: true, + initializeErr: errors.New("err1"), + closeErr: errors.New("err2"), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) + + ss := &spanStore{} + trace.RegisterExporter(ss) + defer trace.UnregisterExporter(ss) + + done, err := obsreporttest.SetupRecordedMetricsTest() + require.NoError(t, err) + defer done() + + initializeChs := make([]chan bool, test.scrapers+test.resourceScrapers) + scrapeMetricsChs := make([]chan int, test.scrapers) + scrapeResourceMetricsChs := make([]chan int, test.resourceScrapers) + closeChs := make([]chan bool, test.scrapers+test.resourceScrapers) + options := configureMetricOptions(test, initializeChs, scrapeMetricsChs, scrapeResourceMetricsChs, closeChs) + + tickerCh := make(chan time.Time) + options = append(options, WithTickerChannel(tickerCh)) + + var nextConsumer consumer.MetricsConsumer + sink := new(consumertest.MetricsSink) + if !test.nilNextConsumer { + nextConsumer = sink + } + defaultCfg := DefaultScraperControllerSettings("receiver") + cfg := &defaultCfg + if test.scraperControllerSettings != nil { + cfg = test.scraperControllerSettings + cfg.NameVal = "receiver" + } + + mr, err := NewScraperControllerReceiver(cfg, zap.NewNop(), nextConsumer, options...) + if test.expectedNewErr != "" { + assert.EqualError(t, err, test.expectedNewErr) + return + } + require.NoError(t, err) + + err = mr.Start(context.Background(), componenttest.NewNopHost()) + expectedStartErr := getExpectedStartErr(test) + if expectedStartErr != nil { + assert.Equal(t, expectedStartErr, err) + } else if test.initialize { + assertChannelsCalled(t, initializeChs, "start was not called") + } + + const iterations = 5 + + if test.expectScraped || test.scrapeErr != nil { + // validate that scrape is called at least N times for each configured scraper + for i := 0; i < iterations; i++ { + tickerCh <- time.Now() + + for _, ch := range scrapeMetricsChs { + <-ch + } + for _, ch := range scrapeResourceMetricsChs { + <-ch + } + } + + // wait until all calls to scrape have completed + if test.scrapeErr == nil { + require.Eventually(t, func() bool { + return sink.MetricsCount() == iterations*(test.scrapers+test.resourceScrapers) + }, time.Second, time.Millisecond) + } + + if test.expectScraped { + assert.GreaterOrEqual(t, sink.MetricsCount(), iterations) + } + + spans := ss.PullAllSpans() + assertReceiverSpan(t, spans) + assertReceiverViews(t, sink) + assertScraperSpan(t, test.scrapeErr, spans) + assertScraperViews(t, test.scrapeErr, sink) + } + + err = mr.Shutdown(context.Background()) + expectedShutdownErr := getExpectedShutdownErr(test) + if expectedShutdownErr != nil { + assert.EqualError(t, err, expectedShutdownErr.Error()) + } else if test.close { + assertChannelsCalled(t, closeChs, "shutdown was not called") + } + }) + } +} + +func configureMetricOptions(test metricsTestCase, initializeChs []chan bool, scrapeMetricsChs, testScrapeResourceMetricsChs []chan int, closeChs []chan bool) []ScraperControllerOption { + var metricOptions []ScraperControllerOption + + for i := 0; i < test.scrapers; i++ { + var scraperOptions []ScraperOption + if test.initialize { + initializeChs[i] = make(chan bool, 1) + ti := &testInitialize{ch: initializeChs[i], err: test.initializeErr} + scraperOptions = append(scraperOptions, WithStart(ti.start)) + } + if test.close { + closeChs[i] = make(chan bool, 1) + tc := &testClose{ch: closeChs[i], err: test.closeErr} + scraperOptions = append(scraperOptions, WithShutdown(tc.shutdown)) + } + + scrapeMetricsChs[i] = make(chan int) + tsm := &testScrapeMetrics{ch: scrapeMetricsChs[i], err: test.scrapeErr} + metricOptions = append(metricOptions, AddMetricsScraper(NewMetricsScraper("scraper", tsm.scrape, scraperOptions...))) + } + + for i := 0; i < test.resourceScrapers; i++ { + var scraperOptions []ScraperOption + if test.initialize { + initializeChs[test.scrapers+i] = make(chan bool, 1) + ti := &testInitialize{ch: initializeChs[test.scrapers+i], err: test.initializeErr} + scraperOptions = append(scraperOptions, WithStart(ti.start)) + } + if test.close { + closeChs[test.scrapers+i] = make(chan bool, 1) + tc := &testClose{ch: closeChs[test.scrapers+i], err: test.closeErr} + scraperOptions = append(scraperOptions, WithShutdown(tc.shutdown)) + } + + testScrapeResourceMetricsChs[i] = make(chan int) + tsrm := &testScrapeResourceMetrics{ch: testScrapeResourceMetricsChs[i], err: test.scrapeErr} + metricOptions = append(metricOptions, AddResourceMetricsScraper(NewResourceMetricsScraper("scraper", tsrm.scrape, scraperOptions...))) + } + + return metricOptions +} + +func getExpectedStartErr(test metricsTestCase) error { + return test.initializeErr +} + +func getExpectedShutdownErr(test metricsTestCase) error { + var errs []error + + if test.closeErr != nil { + for i := 0; i < test.scrapers; i++ { + errs = append(errs, test.closeErr) + } + } + + return componenterror.CombineErrors(errs) +} + +func assertChannelsCalled(t *testing.T, chs []chan bool, message string) { + for _, ic := range chs { + assertChannelCalled(t, ic, message) + } +} + +func assertChannelCalled(t *testing.T, ch chan bool, message string) { + select { + case <-ch: + default: + assert.Fail(t, message) + } +} + +func assertReceiverSpan(t *testing.T, spans []*trace.SpanData) { + receiverSpan := false + for _, span := range spans { + if span.Name == "receiver/receiver/MetricsReceived" { + receiverSpan = true + break + } + } + assert.True(t, receiverSpan) +} + +func assertReceiverViews(t *testing.T, sink *consumertest.MetricsSink) { + dataPointCount := 0 + for _, md := range sink.AllMetrics() { + _, dpc := md.MetricAndDataPointCount() + dataPointCount += dpc + } + obsreporttest.CheckReceiverMetricsViews(t, "receiver", "", int64(dataPointCount), 0) +} + +func assertScraperSpan(t *testing.T, expectedErr error, spans []*trace.SpanData) { + expectedScrapeTraceStatus := trace.Status{Code: trace.StatusCodeOK} + expectedScrapeTraceMessage := "" + if expectedErr != nil { + expectedScrapeTraceStatus = trace.Status{Code: trace.StatusCodeUnknown, Message: expectedErr.Error()} + expectedScrapeTraceMessage = expectedErr.Error() + } + + scraperSpan := false + for _, span := range spans { + if span.Name == "scraper/receiver/scraper/MetricsScraped" { + scraperSpan = true + assert.Equal(t, expectedScrapeTraceStatus, span.Status) + assert.Equal(t, expectedScrapeTraceMessage, span.Message) + break + } + } + assert.True(t, scraperSpan) +} + +func assertScraperViews(t *testing.T, expectedErr error, sink *consumertest.MetricsSink) { + expectedScraped := int64(sink.MetricsCount()) + expectedErrored := int64(0) + if expectedErr != nil { + if partialError, isPartial := expectedErr.(consumererror.PartialScrapeError); isPartial { + expectedErrored = int64(partialError.Failed) + } else { + expectedScraped = int64(0) + expectedErrored = int64(sink.MetricsCount()) + } + } + + obsreporttest.CheckScraperMetricsViews(t, "receiver", "scraper", expectedScraped, expectedErrored) +} + +func singleMetric() pdata.MetricSlice { + metrics := pdata.NewMetricSlice() + metrics.Resize(1) + metrics.At(0).SetDataType(pdata.MetricDataTypeIntGauge) + metrics.At(0).IntGauge().DataPoints().Resize(1) + return metrics +} + +func singleResourceMetric() pdata.ResourceMetricsSlice { + rms := pdata.NewResourceMetricsSlice() + rms.Resize(1) + rm := rms.At(0) + ilms := rm.InstrumentationLibraryMetrics() + ilms.Resize(1) + ilm := ilms.At(0) + singleMetric().MoveAndAppendTo(ilm.Metrics()) + return rms +} + +func TestSingleScrapePerTick(t *testing.T) { + scrapeMetricsCh := make(chan int, 10) + tsm := &testScrapeMetrics{ch: scrapeMetricsCh} + + scrapeResourceMetricsCh := make(chan int, 10) + tsrm := &testScrapeResourceMetrics{ch: scrapeResourceMetricsCh} + + defaultCfg := DefaultScraperControllerSettings("") + cfg := &defaultCfg + + tickerCh := make(chan time.Time) + + receiver, err := NewScraperControllerReceiver( + cfg, + zap.NewNop(), + new(consumertest.MetricsSink), + AddMetricsScraper(NewMetricsScraper("", tsm.scrape)), + AddResourceMetricsScraper(NewResourceMetricsScraper("", tsrm.scrape)), + WithTickerChannel(tickerCh), + ) + require.NoError(t, err) + + require.NoError(t, receiver.Start(context.Background(), componenttest.NewNopHost())) + + tickerCh <- time.Now() + + assert.Equal(t, 1, <-scrapeMetricsCh) + assert.Equal(t, 1, <-scrapeResourceMetricsCh) + + select { + case <-scrapeMetricsCh: + assert.Fail(t, "Scrape was called more than once") + case <-scrapeResourceMetricsCh: + assert.Fail(t, "Scrape was called more than once") + case <-time.After(100 * time.Millisecond): + return + } +} + +type spanStore struct { + sync.Mutex + spans []*trace.SpanData +} + +func (ss *spanStore) ExportSpan(sd *trace.SpanData) { + ss.Lock() + ss.spans = append(ss.spans, sd) + ss.Unlock() +} + +func (ss *spanStore) PullAllSpans() []*trace.SpanData { + ss.Lock() + capturedSpans := ss.spans + ss.spans = nil + ss.Unlock() + return capturedSpans +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/README.md b/internal/otel_collector/receiver/zipkinreceiver/README.md new file mode 100644 index 00000000000..9c73ad6d53f --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/README.md @@ -0,0 +1,29 @@ +# Zipkin Receiver + +This receiver receives spans from [Zipkin](https://zipkin.io/) (V1 and V2). + +Supported pipeline types: traces + +## Getting Started + +All that is required to enable the Zipkin receiver is to include it in the +receiver definitions. + +```yaml +receivers: + zipkin: +``` + +The following settings are configurable: + +- `endpoint` (default = 0.0.0.0:9411): host:port to which the receiver is going + to receive data. The valid syntax is described at + https://github.com/grpc/grpc/blob/master/doc/naming.md. + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configgrpc/README.md) including CORS +- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/config/configtls/README.md) +- [Queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/master/exporter/exporterhelper/README.md) diff --git a/internal/otel_collector/receiver/zipkinreceiver/config.go b/internal/otel_collector/receiver/zipkinreceiver/config.go new file mode 100644 index 00000000000..9df6c11df11 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/config.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" +) + +// Config defines configuration for Zipkin receiver. +type Config struct { + configmodels.ReceiverSettings `mapstructure:",squash"` + + // Configures the receiver server protocol. + confighttp.HTTPServerSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + + // If enabled the zipkin receiver will attempt to parse string tags/binary annotations into int/bool/float. + // Disabled by default + ParseStringTags bool `mapstructure:"parse_string_tags"` +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/config_test.go b/internal/otel_collector/receiver/zipkinreceiver/config_test.go new file mode 100644 index 00000000000..04d5d0eb6c5 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/config_test.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 3) + + r0 := cfg.Receivers["zipkin"] + assert.Equal(t, r0, factory.CreateDefaultConfig()) + + r1 := cfg.Receivers["zipkin/customname"].(*Config) + assert.Equal(t, r1, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "zipkin/customname", + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "localhost:8765", + }, + }) + + r2 := cfg.Receivers["zipkin/parse_strings"].(*Config) + assert.Equal(t, r2, + &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: "zipkin/parse_strings", + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "0.0.0.0:9411", + }, + ParseStringTags: true, + }) +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/factory.go b/internal/otel_collector/receiver/zipkinreceiver/factory.go new file mode 100644 index 00000000000..8d10e5cbc10 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/factory.go @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +// This file implements factory for Zipkin receiver. + +const ( + // The value of "type" key in configuration. + typeStr = "zipkin" + + defaultBindEndpoint = "0.0.0.0:9411" +) + +// NewFactory creates a new Zipkin receiver factory +func NewFactory() component.ReceiverFactory { + return receiverhelper.NewFactory( + typeStr, + createDefaultConfig, + receiverhelper.WithTraces(createTraceReceiver), + ) +} + +// createDefaultConfig creates the default configuration for Zipkin receiver. +func createDefaultConfig() configmodels.Receiver { + return &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + TypeVal: typeStr, + NameVal: typeStr, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultBindEndpoint, + }, + ParseStringTags: false, + } +} + +// createTraceReceiver creates a trace receiver based on provided config. +func createTraceReceiver( + _ context.Context, + _ component.ReceiverCreateParams, + cfg configmodels.Receiver, + nextConsumer consumer.TracesConsumer, +) (component.TracesReceiver, error) { + rCfg := cfg.(*Config) + return New(rCfg, nextConsumer) +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/factory_test.go b/internal/otel_collector/receiver/zipkinreceiver/factory_test.go new file mode 100644 index 00000000000..db1d1fe85b4 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/factory_test.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + cfg := createDefaultConfig() + + tReceiver, err := createTraceReceiver( + context.Background(), + component.ReceiverCreateParams{Logger: zap.NewNop()}, + cfg, + consumertest.NewTracesNop()) + assert.NoError(t, err, "receiver creation failed") + assert.NotNil(t, tReceiver, "receiver creation failed") + + tReceiver, err = createTraceReceiver( + context.Background(), + component.ReceiverCreateParams{Logger: zap.NewNop()}, + cfg, + consumertest.NewTracesNop()) + assert.NoError(t, err, "receiver creation failed") + assert.NotNil(t, tReceiver, "receiver creation failed") +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/proto_parse_test.go b/internal/otel_collector/receiver/zipkinreceiver/proto_parse_test.go new file mode 100644 index 00000000000..dfba6521b7e --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/proto_parse_test.go @@ -0,0 +1,305 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "net/http" + "testing" + "time" + + "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/data" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestConvertSpansToTraceSpans_protobuf(t *testing.T) { + // TODO: (@odeke-em) examine the entire codepath that time goes through + // in Zipkin-Go to ensure that all rounding matches. Otherwise + // for now we need to round Annotation times to seconds for comparison. + cmpTimestamp := func(t time.Time) time.Time { + return t.Round(time.Second) + } + + now := cmpTimestamp(time.Date(2018, 10, 31, 19, 43, 35, 789, time.UTC)) + minus10hr5ms := cmpTimestamp(now.Add(-(10*time.Hour + 5*time.Millisecond))) + + // 1. Generate some spans then serialize them with protobuf + payloadFromWild := &zipkin_proto3.ListOfSpans{ + Spans: []*zipkin_proto3.Span{ + { + TraceId: []byte{0x7F, 0x6F, 0x5F, 0x4F, 0x3F, 0x2F, 0x1F, 0x0F, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}, + Id: []byte{0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}, + ParentId: []byte{0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}, + Name: "ProtoSpan1", + Kind: zipkin_proto3.Span_CONSUMER, + Timestamp: uint64(now.UnixNano() / 1e3), + Duration: 12e6, // 12 seconds + LocalEndpoint: &zipkin_proto3.Endpoint{ + ServiceName: "svc-1", + Ipv4: []byte{0xC0, 0xA8, 0x00, 0x01}, + Port: 8009, + }, + RemoteEndpoint: &zipkin_proto3.Endpoint{ + ServiceName: "memcached", + Ipv6: []byte{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, + Port: 11211, + }, + }, + { + TraceId: []byte{0x7A, 0x6A, 0x5A, 0x4A, 0x3A, 0x2A, 0x1A, 0x0A, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xC0}, + Id: []byte{0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60}, + ParentId: []byte{0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10}, + Name: "CacheWarmUp", + Kind: zipkin_proto3.Span_PRODUCER, + Timestamp: uint64(minus10hr5ms.UnixNano() / 1e3), + Duration: 7e6, // 7 seconds + LocalEndpoint: &zipkin_proto3.Endpoint{ + ServiceName: "search", + Ipv4: []byte{0x0A, 0x00, 0x00, 0x0D}, + Port: 8009, + }, + RemoteEndpoint: &zipkin_proto3.Endpoint{ + ServiceName: "redis", + Ipv6: []byte{0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x53, 0xa7, 0x7c, 0xda, 0x4d, 0xd2, 0x1b}, + Port: 6379, + }, + Annotations: []*zipkin_proto3.Annotation{ + { + Timestamp: uint64(minus10hr5ms.UnixNano() / 1e3), + Value: "DB reset", + }, + { + Timestamp: uint64(minus10hr5ms.UnixNano() / 1e3), + Value: "GC Cycle 39", + }, + }, + }, + }, + } + + // 2. Serialize it + protoBlob, err := proto.Marshal(payloadFromWild) + require.NoError(t, err, "Failed to protobuf serialize payload: %v", err) + zi := new(ZipkinReceiver) + zi.config = createDefaultConfig().(*Config) + hdr := make(http.Header) + hdr.Set("Content-Type", "application/x-protobuf") + + // 3. Get that payload converted to OpenCensus proto spans. + reqs, err := zi.v2ToTraceSpans(protoBlob, hdr) + require.NoError(t, err, "Failed to parse convert Zipkin spans in Protobuf to Trace spans: %v", err) + require.Equal(t, reqs.ResourceSpans().Len(), 2, "Expecting exactly 2 requests since spans have different node/localEndpoint: %v", reqs.ResourceSpans().Len()) + + want := pdata.TracesFromOtlp([]*otlptrace.ResourceSpans{ + { + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: conventions.AttributeServiceName, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "svc-1", + }, + }, + }, + }, + }, + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + TraceId: data.NewTraceID([16]byte{0x7F, 0x6F, 0x5F, 0x4F, 0x3F, 0x2F, 0x1F, 0x0F, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}), + SpanId: data.NewSpanID([8]byte{0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}), + ParentSpanId: data.NewSpanID([8]byte{0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0}), + Name: "ProtoSpan1", + StartTimeUnixNano: uint64(now.UnixNano()), + EndTimeUnixNano: uint64(now.Add(12 * time.Second).UnixNano()), + Attributes: []otlpcommon.KeyValue{ + { + Key: conventions.AttributeNetHostIP, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "192.168.0.1", + }, + }, + }, + { + Key: conventions.AttributeNetHostPort, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_IntValue{ + IntValue: 8009, + }, + }, + }, + { + Key: conventions.AttributeNetPeerName, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "memcached", + }, + }, + }, + { + Key: conventions.AttributeNetPeerIP, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "fe80::1453:a77c:da4d:d21b", + }, + }, + }, + { + Key: conventions.AttributeNetPeerPort, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_IntValue{ + IntValue: 11211, + }, + }, + }, + { + Key: tracetranslator.TagSpanKind, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: string(tracetranslator.OpenTracingSpanKindConsumer), + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: conventions.AttributeServiceName, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "search", + }, + }, + }, + }, + }, + InstrumentationLibrarySpans: []*otlptrace.InstrumentationLibrarySpans{ + { + Spans: []*otlptrace.Span{ + { + TraceId: data.NewTraceID([16]byte{0x7A, 0x6A, 0x5A, 0x4A, 0x3A, 0x2A, 0x1A, 0x0A, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xC0}), + SpanId: data.NewSpanID([8]byte{0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60}), + ParentSpanId: data.NewSpanID([8]byte{0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10}), + Name: "CacheWarmUp", + StartTimeUnixNano: uint64(now.Add(-10 * time.Hour).UnixNano()), + EndTimeUnixNano: uint64(now.Add(-10 * time.Hour).Add(7 * time.Second).UnixNano()), + Attributes: []otlpcommon.KeyValue{ + { + Key: conventions.AttributeNetHostIP, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "10.0.0.13", + }, + }, + }, + { + Key: conventions.AttributeNetHostPort, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_IntValue{ + IntValue: 8009, + }, + }, + }, + { + Key: conventions.AttributeNetPeerName, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "redis", + }, + }, + }, + { + Key: conventions.AttributeNetPeerIP, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "fe80::1453:a77c:da4d:d21b", + }, + }, + }, + { + Key: conventions.AttributeNetPeerPort, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_IntValue{ + IntValue: 6379, + }, + }, + }, + { + Key: tracetranslator.TagSpanKind, + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: string(tracetranslator.OpenTracingSpanKindProducer), + }, + }, + }, + }, + Events: []*otlptrace.Span_Event{ + { + TimeUnixNano: uint64(now.Add(-10 * time.Hour).UnixNano()), + Name: "DB reset", + }, + { + TimeUnixNano: uint64(now.Add(-10 * time.Hour).UnixNano()), + Name: "GC Cycle 39", + }, + }, + }, + }, + }, + }, + }, + }) + + assert.Equal(t, want.SpanCount(), reqs.SpanCount()) + assert.Equal(t, want.ResourceSpans().Len(), reqs.ResourceSpans().Len()) + for i := 0; i < want.ResourceSpans().Len(); i++ { + wantRS := want.ResourceSpans().At(i) + wSvcName, ok := wantRS.Resource().Attributes().Get(conventions.AttributeServiceName) + assert.True(t, ok) + for j := 0; j < reqs.ResourceSpans().Len(); j++ { + reqsRS := reqs.ResourceSpans().At(j) + rSvcName, ok := reqsRS.Resource().Attributes().Get(conventions.AttributeServiceName) + assert.True(t, ok) + if rSvcName.StringVal() == wSvcName.StringVal() { + compareResourceSpans(t, wantRS, reqsRS) + } + } + } +} + +func compareResourceSpans(t *testing.T, wantRS pdata.ResourceSpans, reqsRS pdata.ResourceSpans) { + assert.Equal(t, wantRS.InstrumentationLibrarySpans().Len(), reqsRS.InstrumentationLibrarySpans().Len()) + wantIL := wantRS.InstrumentationLibrarySpans().At(0) + reqsIL := reqsRS.InstrumentationLibrarySpans().At(0) + assert.Equal(t, wantIL.Spans().Len(), reqsIL.Spans().Len()) +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/testdata/config.yaml b/internal/otel_collector/receiver/zipkinreceiver/testdata/config.yaml new file mode 100644 index 00000000000..b2e4ccbcbdd --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/testdata/config.yaml @@ -0,0 +1,20 @@ +receivers: + zipkin: + zipkin/customname: + endpoint: "localhost:8765" + zipkin/parse_strings: + parse_string_tags: true + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [zipkin] + processors: [exampleprocessor] + exporters: [exampleexporter] + diff --git a/internal/otel_collector/receiver/zipkinreceiver/testdata/sample1.json b/internal/otel_collector/receiver/zipkinreceiver/testdata/sample1.json new file mode 100644 index 00000000000..5339b471566 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/testdata/sample1.json @@ -0,0 +1,288 @@ +[{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } +}] diff --git a/internal/otel_collector/receiver/zipkinreceiver/testdata/sample2.json b/internal/otel_collector/receiver/zipkinreceiver/testdata/sample2.json new file mode 100644 index 00000000000..5dcb5b013be --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/testdata/sample2.json @@ -0,0 +1,32 @@ +[ + { + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0" + } + } +] diff --git a/internal/otel_collector/receiver/zipkinreceiver/trace_receiver.go b/internal/otel_collector/receiver/zipkinreceiver/trace_receiver.go new file mode 100644 index 00000000000..dec39db0a96 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/trace_receiver.go @@ -0,0 +1,309 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "compress/gzip" + "compress/zlib" + "context" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "sync" + + "github.com/apache/thrift/lib/go/thrift" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/openzipkin/zipkin-go/proto/zipkin_proto3" + + "go.opentelemetry.io/collector/client" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/translator/trace/zipkin" +) + +const ( + receiverTransportV1Thrift = "http_v1_thrift" + receiverTransportV1JSON = "http_v1_json" + receiverTransportV2JSON = "http_v2_json" + receiverTransportV2PROTO = "http_v2_proto" +) + +var errNextConsumerRespBody = []byte(`"Internal Server Error"`) + +// ZipkinReceiver type is used to handle spans received in the Zipkin format. +type ZipkinReceiver struct { + // mu protects the fields of this struct + mu sync.Mutex + + // addr is the address onto which the HTTP server will be bound + host component.Host + nextConsumer consumer.TracesConsumer + instanceName string + + startOnce sync.Once + stopOnce sync.Once + server *http.Server + config *Config +} + +var _ http.Handler = (*ZipkinReceiver)(nil) + +// New creates a new zipkinreceiver.ZipkinReceiver reference. +func New(config *Config, nextConsumer consumer.TracesConsumer) (*ZipkinReceiver, error) { + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + zr := &ZipkinReceiver{ + nextConsumer: nextConsumer, + instanceName: config.Name(), + config: config, + } + return zr, nil +} + +// Start spins up the receiver's HTTP server and makes the receiver start its processing. +func (zr *ZipkinReceiver) Start(ctx context.Context, host component.Host) error { + if host == nil { + return errors.New("nil host") + } + + zr.mu.Lock() + defer zr.mu.Unlock() + + var err = componenterror.ErrAlreadyStarted + + zr.startOnce.Do(func() { + err = nil + zr.host = host + zr.server = zr.config.HTTPServerSettings.ToServer(zr) + var listener net.Listener + listener, err = zr.config.HTTPServerSettings.ToListener() + if err != nil { + host.ReportFatalError(err) + return + } + go func() { + err = zr.server.Serve(listener) + if err != nil { + host.ReportFatalError(err) + } + }() + }) + + return err +} + +// v1ToTraceSpans parses Zipkin v1 JSON traces and converts them to OpenCensus Proto spans. +func (zr *ZipkinReceiver) v1ToTraceSpans(blob []byte, hdr http.Header) (reqs pdata.Traces, err error) { + if hdr.Get("Content-Type") == "application/x-thrift" { + zSpans, err := deserializeThrift(blob) + if err != nil { + return pdata.NewTraces(), err + } + + return zipkin.V1ThriftBatchToInternalTraces(zSpans) + } + return zipkin.V1JSONBatchToInternalTraces(blob, zr.config.ParseStringTags) +} + +// deserializeThrift decodes Thrift bytes to a list of spans. +// This code comes from jaegertracing/jaeger, ideally we should have imported +// it but this was creating many conflicts so brought the code to here. +// https://github.com/jaegertracing/jaeger/blob/6bc0c122bfca8e737a747826ae60a22a306d7019/model/converter/thrift/zipkin/deserialize.go#L36 +func deserializeThrift(b []byte) ([]*zipkincore.Span, error) { + buffer := thrift.NewTMemoryBuffer() + buffer.Write(b) + + transport := thrift.NewTBinaryProtocolTransport(buffer) + _, size, err := transport.ReadListBegin() // Ignore the returned element type + if err != nil { + return nil, err + } + + // We don't depend on the size returned by ReadListBegin to preallocate the array because it + // sometimes returns a nil error on bad input and provides an unreasonably large int for size + var spans []*zipkincore.Span + for i := 0; i < size; i++ { + zs := &zipkincore.Span{} + if err = zs.Read(transport); err != nil { + return nil, err + } + spans = append(spans, zs) + } + + return spans, nil +} + +// v2ToTraceSpans parses Zipkin v2 JSON or Protobuf traces and converts them to OpenCensus Proto spans. +func (zr *ZipkinReceiver) v2ToTraceSpans(blob []byte, hdr http.Header) (reqs pdata.Traces, err error) { + // This flag's reference is from: + // https://github.com/openzipkin/zipkin-go/blob/3793c981d4f621c0e3eb1457acffa2c1cc591384/proto/v2/zipkin.proto#L154 + debugWasSet := hdr.Get("X-B3-Flags") == "1" + + var zipkinSpans []*zipkinmodel.SpanModel + + // Zipkin can send protobuf via http + switch hdr.Get("Content-Type") { + // TODO: (@odeke-em) record the unique types of Content-Type uploads + case "application/x-protobuf": + zipkinSpans, err = zipkin_proto3.ParseSpans(blob, debugWasSet) + + default: // By default, we'll assume using JSON + zipkinSpans, err = zr.deserializeFromJSON(blob) + } + + if err != nil { + return pdata.Traces{}, err + } + + return zipkin.V2SpansToInternalTraces(zipkinSpans, zr.config.ParseStringTags) +} + +func (zr *ZipkinReceiver) deserializeFromJSON(jsonBlob []byte) (zs []*zipkinmodel.SpanModel, err error) { + if err = json.Unmarshal(jsonBlob, &zs); err != nil { + return nil, err + } + return zs, nil +} + +// Shutdown tells the receiver that should stop reception, +// giving it a chance to perform any necessary clean-up and shutting down +// its HTTP server. +func (zr *ZipkinReceiver) Shutdown(context.Context) error { + var err = componenterror.ErrAlreadyStopped + zr.stopOnce.Do(func() { + err = zr.server.Close() + }) + return err +} + +// processBodyIfNecessary checks the "Content-Encoding" HTTP header and if +// a compression such as "gzip", "deflate", "zlib", is found, the body will +// be uncompressed accordingly or return the body untouched if otherwise. +// Clients such as Zipkin-Java do this behavior e.g. +// send "Content-Encoding":"gzip" of the JSON content. +func processBodyIfNecessary(req *http.Request) io.Reader { + switch req.Header.Get("Content-Encoding") { + default: + return req.Body + + case "gzip": + return gunzippedBodyIfPossible(req.Body) + + case "deflate", "zlib": + return zlibUncompressedbody(req.Body) + } +} + +func gunzippedBodyIfPossible(r io.Reader) io.Reader { + gzr, err := gzip.NewReader(r) + if err != nil { + // Just return the old body as was + return r + } + return gzr +} + +func zlibUncompressedbody(r io.Reader) io.Reader { + zr, err := zlib.NewReader(r) + if err != nil { + // Just return the old body as was + return r + } + return zr +} + +const ( + zipkinV1TagValue = "zipkinV1" + zipkinV2TagValue = "zipkinV2" +) + +// The ZipkinReceiver receives spans from endpoint /api/v2 as JSON, +// unmarshals them and sends them along to the nextConsumer. +func (zr *ZipkinReceiver) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if c, ok := client.FromHTTP(r); ok { + ctx = client.NewContext(ctx, c) + } + + // Now deserialize and process the spans. + asZipkinv1 := r.URL != nil && strings.Contains(r.URL.Path, "api/v1/spans") + + transportTag := transportType(r) + ctx = obsreport.ReceiverContext(ctx, zr.instanceName, transportTag) + ctx = obsreport.StartTraceDataReceiveOp(ctx, zr.instanceName, transportTag) + + pr := processBodyIfNecessary(r) + slurp, _ := ioutil.ReadAll(pr) + if c, ok := pr.(io.Closer); ok { + _ = c.Close() + } + _ = r.Body.Close() + + var td pdata.Traces + var err error + if asZipkinv1 { + td, err = zr.v1ToTraceSpans(slurp, r.Header) + } else { + td, err = zr.v2ToTraceSpans(slurp, r.Header) + } + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + consumerErr := zr.nextConsumer.ConsumeTraces(ctx, td) + + receiverTagValue := zipkinV2TagValue + if asZipkinv1 { + receiverTagValue = zipkinV1TagValue + } + obsreport.EndTraceDataReceiveOp(ctx, receiverTagValue, td.SpanCount(), consumerErr) + + if consumerErr != nil { + // Transient error, due to some internal condition. + w.WriteHeader(http.StatusInternalServerError) + w.Write(errNextConsumerRespBody) + return + } + + // Finally send back the response "Accepted" as + // required at https://zipkin.io/zipkin-api/#/default/post_spans + w.WriteHeader(http.StatusAccepted) +} + +func transportType(r *http.Request) string { + v1 := r.URL != nil && strings.Contains(r.URL.Path, "api/v1/spans") + if v1 { + if r.Header.Get("Content-Type") == "application/x-thrift" { + return receiverTransportV1Thrift + } + return receiverTransportV1JSON + } + if r.Header.Get("Content-Type") == "application/x-protobuf" { + return receiverTransportV2PROTO + } + return receiverTransportV2JSON +} diff --git a/internal/otel_collector/receiver/zipkinreceiver/trace_receiver_test.go b/internal/otel_collector/receiver/zipkinreceiver/trace_receiver_test.go new file mode 100644 index 00000000000..b466277f579 --- /dev/null +++ b/internal/otel_collector/receiver/zipkinreceiver/trace_receiver_test.go @@ -0,0 +1,601 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkinreceiver + +import ( + "bytes" + "compress/gzip" + "compress/zlib" + "context" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + zipkin2 "github.com/jaegertracing/jaeger/model/converter/thrift/zipkin" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/zipkinexporter" + "go.opentelemetry.io/collector/testutil" + "go.opentelemetry.io/collector/translator/conventions" +) + +const zipkinReceiverName = "zipkin_receiver_test" + +func TestNew(t *testing.T) { + type args struct { + address string + nextConsumer consumer.TracesConsumer + } + tests := []struct { + name string + args args + wantErr error + }{ + { + name: "nil nextConsumer", + args: args{}, + wantErr: componenterror.ErrNilNextConsumer, + }, + { + name: "happy path", + args: args{ + nextConsumer: consumertest.NewTracesNop(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: tt.args.address, + }, + } + got, err := New(cfg, tt.args.nextConsumer) + require.Equal(t, tt.wantErr, err) + if tt.wantErr == nil { + require.NotNil(t, got) + } else { + require.Nil(t, got) + } + }) + } +} + +func TestZipkinReceiverPortAlreadyInUse(t *testing.T) { + l, err := net.Listen("tcp", "localhost:") + require.NoError(t, err, "failed to open a port: %v", err) + defer l.Close() + _, portStr, err := net.SplitHostPort(l.Addr().String()) + require.NoError(t, err, "failed to split listener address: %v", err) + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "localhost:" + portStr, + }, + } + traceReceiver, err := New(cfg, consumertest.NewTracesNop()) + require.NoError(t, err, "Failed to create receiver: %v", err) + err = traceReceiver.Start(context.Background(), componenttest.NewNopHost()) + require.Error(t, err) +} + +func TestConvertSpansToTraceSpans_json(t *testing.T) { + // Using Adrian Cole's sample at https://gist.github.com/adriancole/e8823c19dfed64e2eb71 + blob, err := ioutil.ReadFile("./testdata/sample1.json") + require.NoError(t, err, "Failed to read sample JSON file: %v", err) + zi := new(ZipkinReceiver) + zi.config = createDefaultConfig().(*Config) + reqs, err := zi.v2ToTraceSpans(blob, nil) + require.NoError(t, err, "Failed to parse convert Zipkin spans in JSON to Trace spans: %v", err) + + require.Equal(t, 1, reqs.ResourceSpans().Len(), "Expecting only one request since all spans share same node/localEndpoint: %v", reqs.ResourceSpans().Len()) + + req := reqs.ResourceSpans().At(0) + sn, _ := req.Resource().Attributes().Get(conventions.AttributeServiceName) + assert.Equal(t, "frontend", sn.StringVal()) + + // Expecting 9 non-nil spans + require.Equal(t, 9, reqs.SpanCount(), "Incorrect non-nil spans count") +} + +func TestConversionRoundtrip(t *testing.T) { + // The goal is to convert from: + // 1. Original Zipkin JSON as that's the format that Zipkin receivers will receive + // 2. Into TraceProtoSpans + // 3. Into SpanData + // 4. Back into Zipkin JSON (in this case the Zipkin exporter has been configured) + receiverInputJSON := []byte(` +[{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::80:807f" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0", + "status.code": "STATUS_CODE_UNSET" + } +}, +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91386", + "id": "4d1e00c0db9010db", + "kind": "SERVER", + "name": "put", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::80:807f" + }, + "remoteEndpoint": { + "serviceName": "frontend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "clnt/finagle.version": "6.45.0", + "status.code": "STATUS_CODE_UNSET" + } +}]`) + + zi := &ZipkinReceiver{nextConsumer: consumertest.NewTracesNop()} + zi.config = &Config{} + ereqs, err := zi.v2ToTraceSpans(receiverInputJSON, nil) + require.NoError(t, err) + + require.Equal(t, 2, ereqs.SpanCount()) + + // Now the last phase is to transmit them over the wire and then compare the JSONs + + buf := new(bytes.Buffer) + // This will act as the final Zipkin server. + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.Copy(buf, r.Body) + _ = r.Body.Close() + })) + defer backend.Close() + + factory := zipkinexporter.NewFactory() + config := factory.CreateDefaultConfig().(*zipkinexporter.Config) + config.Endpoint = backend.URL + params := component.ExporterCreateParams{Logger: zap.NewNop()} + ze, err := factory.CreateTracesExporter(context.Background(), params, config) + require.NoError(t, err) + require.NotNil(t, ze) + require.NoError(t, ze.Start(context.Background(), componenttest.NewNopHost())) + + require.NoError(t, ze.ConsumeTraces(context.Background(), ereqs)) + + // Shutdown the exporter so it can flush any remaining data. + assert.NoError(t, ze.Shutdown(context.Background())) + backend.Close() + + // The received JSON messages are inside arrays, so reading then directly will + // fail with error. Use a small hack to transform the multiple arrays into a + // single one. + accumulatedJSONMsgs := strings.ReplaceAll(buf.String(), "][", ",") + gj := testutil.GenerateNormalizedJSON(t, accumulatedJSONMsgs) + wj := testutil.GenerateNormalizedJSON(t, string(receiverInputJSON)) + // translation to OTLP sorts spans so do a span-by-span comparison + gj = gj[1 : len(gj)-1] + wj = wj[1 : len(wj)-1] + gjSpans := strings.Split(gj, "{\"annotations\":") + wjSpans := strings.Split(wj, "{\"annotations\":") + assert.Equal(t, len(wjSpans), len(gjSpans)) + for _, wjspan := range wjSpans { + if len(wjspan) > 3 && wjspan[len(wjspan)-1:] == "," { + wjspan = wjspan[0 : len(wjspan)-1] + } + matchFound := false + for _, gjspan := range gjSpans { + if len(gjspan) > 3 && gjspan[len(gjspan)-1:] == "," { + gjspan = gjspan[0 : len(gjspan)-1] + } + if wjspan == gjspan { + matchFound = true + } + } + assert.True(t, matchFound, fmt.Sprintf("no match found for {\"annotations\":%s %v", wjspan, gjSpans)) + } +} + +func TestStartTraceReception(t *testing.T) { + tests := []struct { + name string + host component.Host + wantErr bool + }{ + { + name: "nil_host", + wantErr: true, + }, + { + name: "valid_host", + host: componenttest.NewNopHost(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sink := new(consumertest.TracesSink) + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "localhost:0", + }, + } + zr, err := New(cfg, sink) + require.Nil(t, err) + require.NotNil(t, zr) + + err = zr.Start(context.Background(), tt.host) + assert.Equal(t, tt.wantErr, err != nil) + if !tt.wantErr { + require.Nil(t, zr.Shutdown(context.Background())) + } + }) + } +} + +func TestReceiverContentTypes(t *testing.T) { + tests := []struct { + endpoint string + content string + encoding string + bodyFn func() ([]byte, error) + }{ + { + endpoint: "/api/v1/spans", + content: "application/json", + encoding: "gzip", + bodyFn: func() ([]byte, error) { + return ioutil.ReadFile("../../translator/trace/zipkin/testdata/zipkin_v1_single_batch.json") + }, + }, + + { + endpoint: "/api/v1/spans", + content: "application/x-thrift", + encoding: "gzip", + bodyFn: func() ([]byte, error) { + return thriftExample(), nil + }, + }, + + { + endpoint: "/api/v2/spans", + content: "application/json", + encoding: "gzip", + bodyFn: func() ([]byte, error) { + return ioutil.ReadFile("../../translator/trace/zipkin/testdata/zipkin_v2_single.json") + }, + }, + + { + endpoint: "/api/v2/spans", + content: "application/json", + encoding: "zlib", + bodyFn: func() ([]byte, error) { + return ioutil.ReadFile("../../translator/trace/zipkin/testdata/zipkin_v2_single.json") + }, + }, + + { + endpoint: "/api/v2/spans", + content: "application/json", + encoding: "", + bodyFn: func() ([]byte, error) { + return ioutil.ReadFile("../../translator/trace/zipkin/testdata/zipkin_v2_single.json") + }, + }, + } + + for _, test := range tests { + name := fmt.Sprintf("%v %v %v", test.endpoint, test.content, test.encoding) + t.Run(name, func(t *testing.T) { + body, err := test.bodyFn() + require.NoError(t, err, "Failed to generate test body: %v", err) + + var requestBody *bytes.Buffer + switch test.encoding { + case "": + requestBody = bytes.NewBuffer(body) + case "zlib": + requestBody, err = compressZlib(body) + case "gzip": + requestBody, err = compressGzip(body) + } + require.NoError(t, err) + + r := httptest.NewRequest("POST", test.endpoint, requestBody) + r.Header.Add("content-type", test.content) + r.Header.Add("content-encoding", test.encoding) + + next := &zipkinMockTraceConsumer{ + ch: make(chan pdata.Traces, 10), + } + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "", + }, + } + zr, err := New(cfg, next) + require.NoError(t, err) + + req := httptest.NewRecorder() + zr.ServeHTTP(req, r) + + select { + case td := <-next.ch: + require.NotNil(t, td) + require.Equal(t, 202, req.Code) + break + case <-time.After(time.Second * 2): + t.Error("next consumer did not receive the batch") + break + } + }) + } +} + +func TestReceiverInvalidContentType(t *testing.T) { + body := `{ invalid json ` + + r := httptest.NewRequest("POST", "/api/v2/spans", + bytes.NewBuffer([]byte(body))) + r.Header.Add("content-type", "application/json") + + next := &zipkinMockTraceConsumer{ + ch: make(chan pdata.Traces, 10), + } + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "", + }, + } + zr, err := New(cfg, next) + require.NoError(t, err) + + req := httptest.NewRecorder() + zr.ServeHTTP(req, r) + + require.Equal(t, 400, req.Code) + require.Equal(t, "invalid character 'i' looking for beginning of object key string\n", req.Body.String()) +} + +func TestReceiverConsumerError(t *testing.T) { + body, err := ioutil.ReadFile("../../translator/trace/zipkin/testdata/zipkin_v2_single.json") + require.NoError(t, err) + + r := httptest.NewRequest("POST", "/api/v2/spans", bytes.NewBuffer(body)) + r.Header.Add("content-type", "application/json") + + next := &zipkinMockTraceConsumer{ + ch: make(chan pdata.Traces, 10), + err: errors.New("consumer error"), + } + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "localhost:9411", + }, + } + zr, err := New(cfg, next) + require.NoError(t, err) + + req := httptest.NewRecorder() + zr.ServeHTTP(req, r) + + require.Equal(t, 500, req.Code) + require.Equal(t, "\"Internal Server Error\"", req.Body.String()) +} + +func thriftExample() []byte { + now := time.Now().Unix() + zSpans := []*zipkincore.Span{ + { + TraceID: 1, + Name: "test", + ID: 2, + BinaryAnnotations: []*zipkincore.BinaryAnnotation{ + { + Key: "http.path", + Value: []byte("/"), + }, + }, + Timestamp: &now, + }, + } + + return zipkin2.SerializeThrift(zSpans) +} + +func compressGzip(body []byte) (*bytes.Buffer, error) { + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + + _, err := zw.Write(body) + if err != nil { + return nil, err + } + + if err := zw.Close(); err != nil { + return nil, err + } + + return &buf, nil +} + +func compressZlib(body []byte) (*bytes.Buffer, error) { + var buf bytes.Buffer + zw := zlib.NewWriter(&buf) + + _, err := zw.Write(body) + if err != nil { + return nil, err + } + + if err := zw.Close(); err != nil { + return nil, err + } + + return &buf, nil +} + +type zipkinMockTraceConsumer struct { + ch chan pdata.Traces + err error +} + +func (m *zipkinMockTraceConsumer) ConsumeTraces(_ context.Context, td pdata.Traces) error { + m.ch <- td + return m.err +} + +func TestConvertSpansToTraceSpans_JSONWithoutSerivceName(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/sample2.json") + require.NoError(t, err, "Failed to read sample JSON file: %v", err) + zi := new(ZipkinReceiver) + zi.config = createDefaultConfig().(*Config) + reqs, err := zi.v2ToTraceSpans(blob, nil) + require.NoError(t, err, "Failed to parse convert Zipkin spans in JSON to Trace spans: %v", err) + + require.Equal(t, 1, reqs.ResourceSpans().Len(), "Expecting only one request since all spans share same node/localEndpoint: %v", reqs.ResourceSpans().Len()) + + // Expecting 1 non-nil spans + require.Equal(t, 1, reqs.SpanCount(), "Incorrect non-nil spans count") +} + +func TestReceiverConvertsStringsToTypes(t *testing.T) { + body, err := ioutil.ReadFile("../../translator/trace/zipkin/testdata/zipkin_v2_single.json") + require.NoError(t, err, "Failed to read sample JSON file: %v", err) + + r := httptest.NewRequest("POST", "/api/v2/spans", bytes.NewBuffer(body)) + r.Header.Add("content-type", "application/json") + + next := &zipkinMockTraceConsumer{ + ch: make(chan pdata.Traces, 10), + } + cfg := &Config{ + ReceiverSettings: configmodels.ReceiverSettings{ + NameVal: zipkinReceiverName, + }, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "", + }, + ParseStringTags: true, + } + zr, err := New(cfg, next) + require.NoError(t, err) + + req := httptest.NewRecorder() + zr.ServeHTTP(req, r) + + select { + case td := <-next.ch: + require.NotNil(t, td) + require.Equal(t, 202, req.Code) + + span := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + + expected := pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + "cache_hit": pdata.NewAttributeValueBool(true), + "ping_count": pdata.NewAttributeValueInt(25), + "timeout": pdata.NewAttributeValueDouble(12.3), + "clnt/finagle.version": pdata.NewAttributeValueString("6.45.0"), + "http.path": pdata.NewAttributeValueString("/api"), + "http.status_code": pdata.NewAttributeValueInt(500), + "net.host.ip": pdata.NewAttributeValueString("7::80:807f"), + "peer.service": pdata.NewAttributeValueString("backend"), + "net.peer.ip": pdata.NewAttributeValueString("192.168.99.101"), + "net.peer.port": pdata.NewAttributeValueInt(9000), + }).Sort() + + actual := span.Attributes().Sort() + + assert.EqualValues(t, expected, actual) + break + case <-time.After(time.Second * 2): + t.Error("next consumer did not receive the batch") + break + } +} diff --git a/internal/otel_collector/service/builder/builder.go b/internal/otel_collector/service/builder/builder.go new file mode 100644 index 00000000000..be65afd39fe --- /dev/null +++ b/internal/otel_collector/service/builder/builder.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "flag" + "fmt" +) + +const ( + // flags + configCfg = "config" + memBallastFlag = "mem-ballast-size-mib" + + kindLogKey = "component_kind" + kindLogsReceiver = "receiver" + kindLogsProcessor = "processor" + kindLogsExporter = "exporter" + kindLogExtension = "extension" + typeLogKey = "component_type" + nameLogKey = "component_name" +) + +var ( + configFile *string + memBallastSize *uint +) + +// Flags adds flags related to basic building of the collector application to the given flagset. +func Flags(flags *flag.FlagSet) { + configFile = flags.String(configCfg, "", "Path to the config file") + memBallastSize = flags.Uint(memBallastFlag, 0, + fmt.Sprintf("Flag to specify size of memory (MiB) ballast to set. Ballast is not used when this is not specified. "+ + "default settings: 0")) +} + +// GetConfigFile gets the config file from the config file flag. +func GetConfigFile() string { + return *configFile +} + +// MemBallastSize returns the size of memory ballast to use in MBs +func MemBallastSize() int { + return int(*memBallastSize) +} diff --git a/internal/otel_collector/service/builder/doc.go b/internal/otel_collector/service/builder/doc.go new file mode 100644 index 00000000000..c5bd4259703 --- /dev/null +++ b/internal/otel_collector/service/builder/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package builder handles the options to build the OpenTelemetry collector +// pipeline. +package builder diff --git a/internal/otel_collector/service/builder/exporters_builder.go b/internal/otel_collector/service/builder/exporters_builder.go new file mode 100644 index 00000000000..2fcd9174b65 --- /dev/null +++ b/internal/otel_collector/service/builder/exporters_builder.go @@ -0,0 +1,320 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" +) + +// builtExporter is an exporter that is built based on a config. It can have +// a trace and/or a metrics consumer and have a shutdown function. +type builtExporter struct { + logger *zap.Logger + expByDataType map[configmodels.DataType]component.Exporter +} + +// Start the exporter. +func (bexp *builtExporter) Start(ctx context.Context, host component.Host) error { + var errors []error + for _, exporter := range bexp.expByDataType { + err := exporter.Start(ctx, host) + if err != nil { + errors = append(errors, err) + } + } + + return componenterror.CombineErrors(errors) +} + +// Shutdown the trace component and the metrics component of an exporter. +func (bexp *builtExporter) Shutdown(ctx context.Context) error { + var errors []error + for _, exporter := range bexp.expByDataType { + err := exporter.Shutdown(ctx) + if err != nil { + errors = append(errors, err) + } + } + + return componenterror.CombineErrors(errors) +} + +func (bexp *builtExporter) getTraceExporter() component.TracesExporter { + exp := bexp.expByDataType[configmodels.TracesDataType] + if exp == nil { + return nil + } + return exp.(component.TracesExporter) +} + +func (bexp *builtExporter) getMetricExporter() component.MetricsExporter { + exp := bexp.expByDataType[configmodels.MetricsDataType] + if exp == nil { + return nil + } + return exp.(component.MetricsExporter) +} + +func (bexp *builtExporter) getLogExporter() component.LogsExporter { + exp := bexp.expByDataType[configmodels.LogsDataType] + if exp == nil { + return nil + } + return exp.(component.LogsExporter) +} + +// Exporters is a map of exporters created from exporter configs. +type Exporters map[configmodels.Exporter]*builtExporter + +// StartAll starts all exporters. +func (exps Exporters) StartAll(ctx context.Context, host component.Host) error { + for _, exp := range exps { + exp.logger.Info("Exporter is starting...") + + if err := exp.Start(ctx, host); err != nil { + return err + } + exp.logger.Info("Exporter started.") + } + return nil +} + +// ShutdownAll stops all exporters. +func (exps Exporters) ShutdownAll(ctx context.Context) error { + var errs []error + for _, exp := range exps { + err := exp.Shutdown(ctx) + if err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} + +func (exps Exporters) ToMapByDataType() map[configmodels.DataType]map[configmodels.Exporter]component.Exporter { + + exportersMap := make(map[configmodels.DataType]map[configmodels.Exporter]component.Exporter) + + exportersMap[configmodels.TracesDataType] = make(map[configmodels.Exporter]component.Exporter, len(exps)) + exportersMap[configmodels.MetricsDataType] = make(map[configmodels.Exporter]component.Exporter, len(exps)) + exportersMap[configmodels.LogsDataType] = make(map[configmodels.Exporter]component.Exporter, len(exps)) + + for cfg, bexp := range exps { + for t, exp := range bexp.expByDataType { + exportersMap[t][cfg] = exp + } + } + + return exportersMap +} + +type dataTypeRequirement struct { + // Pipeline that requires the data type. + requiredBy *configmodels.Pipeline +} + +// Map of data type requirements. +type dataTypeRequirements map[configmodels.DataType]dataTypeRequirement + +// Data type requirements for all exporters. +type exportersRequiredDataTypes map[configmodels.Exporter]dataTypeRequirements + +// ExportersBuilder builds exporters from config. +type ExportersBuilder struct { + logger *zap.Logger + appInfo component.ApplicationStartInfo + config *configmodels.Config + factories map[configmodels.Type]component.ExporterFactory +} + +// NewExportersBuilder creates a new ExportersBuilder. Call BuildExporters() on the returned value. +func NewExportersBuilder( + logger *zap.Logger, + appInfo component.ApplicationStartInfo, + config *configmodels.Config, + factories map[configmodels.Type]component.ExporterFactory, +) *ExportersBuilder { + return &ExportersBuilder{logger.With(zap.String(kindLogKey, kindLogsExporter)), appInfo, config, factories} +} + +// BuildExporters exporters from config. +func (eb *ExportersBuilder) Build() (Exporters, error) { + exporters := make(Exporters) + + // We need to calculate required input data types for each exporter so that we know + // which data type must be started for each exporter. + exporterInputDataTypes := eb.calcExportersRequiredDataTypes() + + // BuildExporters exporters based on configuration and required input data types. + for _, cfg := range eb.config.Exporters { + componentLogger := eb.logger.With(zap.String(typeLogKey, string(cfg.Type())), zap.String(nameLogKey, cfg.Name())) + exp, err := eb.buildExporter(context.Background(), componentLogger, eb.appInfo, cfg, exporterInputDataTypes) + if err != nil { + return nil, err + } + + exporters[cfg] = exp + } + + return exporters, nil +} + +func (eb *ExportersBuilder) calcExportersRequiredDataTypes() exportersRequiredDataTypes { + + // Go over all pipelines. The data type of the pipeline defines what data type + // each exporter is expected to receive. Collect all required types for each + // exporter. + // + // We also remember the last pipeline that requested the particular data type. + // This is only needed for logging purposes in error cases when we need to + // print that a particular exporter does not support the data type required for + // a particular pipeline. + + result := make(exportersRequiredDataTypes) + + // Iterate over pipelines. + for _, pipeline := range eb.config.Service.Pipelines { + // Iterate over all exporters for this pipeline. + for _, expName := range pipeline.Exporters { + // Find the exporter config by name. + exporter := eb.config.Exporters[expName] + + // Create the data type requirement for the exporter if it does not exist. + if result[exporter] == nil { + result[exporter] = make(dataTypeRequirements) + } + + // Remember that this data type is required for the exporter and also which + // pipeline the requirement is coming from. + result[exporter][pipeline.InputType] = dataTypeRequirement{pipeline} + } + } + return result +} + +func (eb *ExportersBuilder) buildExporter( + ctx context.Context, + logger *zap.Logger, + appInfo component.ApplicationStartInfo, + config configmodels.Exporter, + exportersInputDataTypes exportersRequiredDataTypes, +) (*builtExporter, error) { + factory := eb.factories[config.Type()] + if factory == nil { + return nil, fmt.Errorf("exporter factory not found for type: %s", config.Type()) + } + + exporter := &builtExporter{ + logger: logger, + expByDataType: make(map[configmodels.DataType]component.Exporter, 3), + } + + inputDataTypes := exportersInputDataTypes[config] + if inputDataTypes == nil { + eb.logger.Info("Ignoring exporter as it is not used by any pipeline") + return exporter, nil + } + + creationParams := component.ExporterCreateParams{ + Logger: logger, + ApplicationStartInfo: appInfo, + } + + for dataType, requirement := range inputDataTypes { + switch dataType { + case configmodels.TracesDataType: + // Traces data type is required. Create a trace exporter based on config. + te, err := factory.CreateTracesExporter(ctx, creationParams, config) + if err != nil { + if err == configerror.ErrDataTypeIsNotSupported { + // Could not create because this exporter does not support this data type. + return nil, exporterTypeMismatchErr(config, requirement.requiredBy, dataType) + } + return nil, fmt.Errorf("error creating %s exporter: %v", config.Name(), err) + } + + // Check if the factory really created the exporter. + if te == nil { + return nil, fmt.Errorf("factory for %q produced a nil exporter", config.Name()) + } + + exporter.expByDataType[configmodels.TracesDataType] = te + + case configmodels.MetricsDataType: + // Metrics data type is required. Create a trace exporter based on config. + me, err := factory.CreateMetricsExporter(ctx, creationParams, config) + if err != nil { + if err == configerror.ErrDataTypeIsNotSupported { + // Could not create because this exporter does not support this data type. + return nil, exporterTypeMismatchErr(config, requirement.requiredBy, dataType) + } + return nil, fmt.Errorf("error creating %s exporter: %v", config.Name(), err) + } + + // The factories can be implemented by third parties, check if they really + // created the exporter. + if me == nil { + return nil, fmt.Errorf("factory for %q produced a nil exporter", config.Name()) + } + + exporter.expByDataType[configmodels.MetricsDataType] = me + + case configmodels.LogsDataType: + le, err := factory.CreateLogsExporter(ctx, creationParams, config) + if err != nil { + if err == configerror.ErrDataTypeIsNotSupported { + // Could not create because this exporter does not support this data type. + return nil, exporterTypeMismatchErr(config, requirement.requiredBy, dataType) + } + return nil, fmt.Errorf("error creating %s exporter: %v", config.Name(), err) + } + + // Check if the factory really created the exporter. + if le == nil { + return nil, fmt.Errorf("factory for %q produced a nil exporter", config.Name()) + } + + exporter.expByDataType[configmodels.LogsDataType] = le + + default: + // Could not create because this exporter does not support this data type. + return nil, exporterTypeMismatchErr(config, requirement.requiredBy, dataType) + } + } + + eb.logger.Info("Exporter is enabled.", zap.String("exporter", config.Name())) + + return exporter, nil +} + +func exporterTypeMismatchErr( + config configmodels.Exporter, + requiredByPipeline *configmodels.Pipeline, + dataType configmodels.DataType, +) error { + return fmt.Errorf("pipeline %q of data type %q has an exporter %q, which does not support that data type", + requiredByPipeline.Name, dataType, + config.Name(), + ) +} diff --git a/internal/otel_collector/service/builder/exporters_builder_test.go b/internal/otel_collector/service/builder/exporters_builder_test.go new file mode 100644 index 00000000000..68f92e2647f --- /dev/null +++ b/internal/otel_collector/service/builder/exporters_builder_test.go @@ -0,0 +1,274 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/opencensusexporter" +) + +func TestExportersBuilder_Build(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + oceFactory := opencensusexporter.NewFactory() + factories.Exporters[oceFactory.Type()] = oceFactory + cfg := &configmodels.Config{ + Exporters: map[string]configmodels.Exporter{ + "opencensus": &opencensusexporter.Config{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "opencensus", + TypeVal: "opencensus", + }, + GRPCClientSettings: configgrpc.GRPCClientSettings{ + Endpoint: "0.0.0.0:12345", + }, + NumWorkers: 2, + }, + }, + + Service: configmodels.Service{ + Pipelines: map[string]*configmodels.Pipeline{ + "trace": { + Name: "trace", + InputType: configmodels.TracesDataType, + Exporters: []string{"opencensus"}, + }, + }, + }, + } + + exporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + + assert.NoError(t, err) + require.NotNil(t, exporters) + + e1 := exporters[cfg.Exporters["opencensus"]] + + // Ensure exporter has its fields correctly populated. + require.NotNil(t, e1) + assert.NotNil(t, e1.getTraceExporter()) + assert.Nil(t, e1.getMetricExporter()) + assert.Nil(t, e1.getLogExporter()) + + // Ensure it can be started. + assert.NoError(t, exporters.StartAll(context.Background(), componenttest.NewNopHost())) + + // Ensure it can be stopped. + if err = e1.Shutdown(context.Background()); err != nil { + // TODO Find a better way to handle this case + // Since the endpoint of opencensus exporter doesn't actually exist, e1 may + // already stop because it cannot connect. + // The test should stop running if this isn't the error cause. + require.Equal(t, err.Error(), "rpc error: code = Canceled desc = grpc: the client connection is closing") + } + + // Remove the pipeline so that the exporter is not attached to any pipeline. + // This should result in creating an exporter that has none of consumption + // functions set. + delete(cfg.Service.Pipelines, "trace") + exporters, err = NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NotNil(t, exporters) + assert.NoError(t, err) + + e1 = exporters[cfg.Exporters["opencensus"]] + + // Ensure exporter has its fields correctly populated, ie Trace Exporter and + // Metrics Exporter are nil. + require.NotNil(t, e1) + assert.Nil(t, e1.getTraceExporter()) + assert.Nil(t, e1.getMetricExporter()) + assert.Nil(t, e1.getLogExporter()) + + // TODO: once we have an exporter that supports metrics data type test it too. +} + +func TestExportersBuilder_BuildLogs(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.Nil(t, err) + + cfg := &configmodels.Config{ + Exporters: map[string]configmodels.Exporter{ + "exampleexporter": &componenttest.ExampleExporter{ + ExporterSettings: configmodels.ExporterSettings{ + NameVal: "exampleexporter", + TypeVal: "exampleexporter", + }, + }, + }, + + Service: configmodels.Service{ + Pipelines: map[string]*configmodels.Pipeline{ + "logs": { + Name: "logs", + InputType: "logs", + Exporters: []string{"exampleexporter"}, + }, + }, + }, + } + + exporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + + assert.NoError(t, err) + require.NotNil(t, exporters) + + e1 := exporters[cfg.Exporters["exampleexporter"]] + + // Ensure exporter has its fields correctly populated. + require.NotNil(t, e1) + assert.NotNil(t, e1.getLogExporter()) + assert.Nil(t, e1.getTraceExporter()) + assert.Nil(t, e1.getMetricExporter()) + + // Ensure it can be started. + err = exporters.StartAll(context.Background(), componenttest.NewNopHost()) + assert.NoError(t, err) + + // Ensure it can be stopped. + err = e1.Shutdown(context.Background()) + assert.NoError(t, err) + + // Remove the pipeline so that the exporter is not attached to any pipeline. + // This should result in creating an exporter that has none of consumption + // functions set. + delete(cfg.Service.Pipelines, "logs") + exporters, err = NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NotNil(t, exporters) + assert.Nil(t, err) + + e1 = exporters[cfg.Exporters["exampleexporter"]] + + // Ensure exporter has its fields correctly populated, ie Trace Exporter and + // Metrics Exporter are nil. + require.NotNil(t, e1) + assert.Nil(t, e1.getTraceExporter()) + assert.Nil(t, e1.getMetricExporter()) + assert.Nil(t, e1.getLogExporter()) +} + +func TestExportersBuilder_StartAll(t *testing.T) { + exporters := make(Exporters) + expCfg := &configmodels.ExporterSettings{} + traceExporter := &componenttest.ExampleExporterConsumer{} + metricExporter := &componenttest.ExampleExporterConsumer{} + logsExporter := &componenttest.ExampleExporterConsumer{} + exporters[expCfg] = &builtExporter{ + logger: zap.NewNop(), + expByDataType: map[configmodels.DataType]component.Exporter{ + configmodels.TracesDataType: traceExporter, + configmodels.MetricsDataType: metricExporter, + configmodels.LogsDataType: logsExporter, + }, + } + assert.False(t, traceExporter.ExporterStarted) + assert.False(t, metricExporter.ExporterStarted) + assert.False(t, logsExporter.ExporterStarted) + + assert.NoError(t, exporters.StartAll(context.Background(), componenttest.NewNopHost())) + + assert.True(t, traceExporter.ExporterStarted) + assert.True(t, metricExporter.ExporterStarted) + assert.True(t, logsExporter.ExporterStarted) +} + +func TestExportersBuilder_StopAll(t *testing.T) { + exporters := make(Exporters) + expCfg := &configmodels.ExporterSettings{} + traceExporter := &componenttest.ExampleExporterConsumer{} + metricExporter := &componenttest.ExampleExporterConsumer{} + logsExporter := &componenttest.ExampleExporterConsumer{} + exporters[expCfg] = &builtExporter{ + logger: zap.NewNop(), + expByDataType: map[configmodels.DataType]component.Exporter{ + configmodels.TracesDataType: traceExporter, + configmodels.MetricsDataType: metricExporter, + configmodels.LogsDataType: logsExporter, + }, + } + assert.False(t, traceExporter.ExporterShutdown) + assert.False(t, metricExporter.ExporterShutdown) + assert.False(t, logsExporter.ExporterShutdown) + assert.NoError(t, exporters.ShutdownAll(context.Background())) + + assert.True(t, traceExporter.ExporterShutdown) + assert.True(t, metricExporter.ExporterShutdown) + assert.True(t, logsExporter.ExporterShutdown) +} + +func TestExportersBuilder_ErrorOnNilExporter(t *testing.T) { + bf := newBadExporterFactory() + fm := map[configmodels.Type]component.ExporterFactory{ + bf.Type(): bf, + } + + pipelines := []*configmodels.Pipeline{ + { + Name: "trace", + InputType: configmodels.TracesDataType, + Exporters: []string{string(bf.Type())}, + }, + { + Name: "metrics", + InputType: configmodels.MetricsDataType, + Exporters: []string{string(bf.Type())}, + }, + { + Name: "logs", + InputType: configmodels.LogsDataType, + Exporters: []string{string(bf.Type())}, + }, + } + + for _, pipeline := range pipelines { + t.Run(pipeline.Name, func(t *testing.T) { + + cfg := &configmodels.Config{ + Exporters: map[string]configmodels.Exporter{ + string(bf.Type()): &configmodels.ExporterSettings{ + TypeVal: bf.Type(), + }, + }, + + Service: configmodels.Service{ + Pipelines: map[string]*configmodels.Pipeline{ + pipeline.Name: pipeline, + }, + }, + } + + exporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, fm).Build() + assert.Error(t, err) + assert.Zero(t, len(exporters)) + }) + } +} + +func newBadExporterFactory() component.ExporterFactory { + return exporterhelper.NewFactory("bf", func() configmodels.Exporter { + return &configmodels.ExporterSettings{} + }) +} diff --git a/internal/otel_collector/service/builder/extensions_builder.go b/internal/otel_collector/service/builder/extensions_builder.go new file mode 100644 index 00000000000..8c10d9fb633 --- /dev/null +++ b/internal/otel_collector/service/builder/extensions_builder.go @@ -0,0 +1,181 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configmodels" +) + +// builtExporter is an exporter that is built based on a config. It can have +// a trace and/or a metrics consumer and have a shutdown function. +type builtExtension struct { + logger *zap.Logger + extension component.ServiceExtension +} + +// Start the receiver. +func (ext *builtExtension) Start(ctx context.Context, host component.Host) error { + return ext.extension.Start(ctx, host) +} + +// Stop the receiver. +func (ext *builtExtension) Shutdown(ctx context.Context) error { + return ext.extension.Shutdown(ctx) +} + +var _ component.ServiceExtension = (*builtExtension)(nil) + +// Exporters is a map of exporters created from exporter configs. +type Extensions map[configmodels.Extension]*builtExtension + +// StartAll starts all exporters. +func (exts Extensions) StartAll(ctx context.Context, host component.Host) error { + for _, ext := range exts { + ext.logger.Info("Extension is starting...") + + if err := ext.Start(ctx, host); err != nil { + return err + } + + ext.logger.Info("Extension started.") + } + return nil +} + +// ShutdownAll stops all exporters. +func (exts Extensions) ShutdownAll(ctx context.Context) error { + var errs []error + for _, ext := range exts { + err := ext.Shutdown(ctx) + if err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} + +func (exts Extensions) NotifyPipelineReady() error { + for _, ext := range exts { + if pw, ok := ext.extension.(component.PipelineWatcher); ok { + if err := pw.Ready(); err != nil { + ext.logger.Error("Error notifying extension that the pipeline was started.") + return err + } + } + } + + return nil +} + +func (exts Extensions) NotifyPipelineNotReady() error { + // Notify extensions in reverse order. + var errs []error + for _, ext := range exts { + if pw, ok := ext.extension.(component.PipelineWatcher); ok { + if err := pw.NotReady(); err != nil { + ext.logger.Error("Error notifying extension that the pipeline was shutdown.") + errs = append(errs, err) + } + } + } + + return componenterror.CombineErrors(errs) +} + +func (exts Extensions) ToMap() map[configmodels.Extension]component.ServiceExtension { + result := make(map[configmodels.Extension]component.ServiceExtension, len(exts)) + for k, v := range exts { + result[k] = v.extension + } + return result +} + +// ExportersBuilder builds exporters from config. +type ExtensionsBuilder struct { + logger *zap.Logger + appInfo component.ApplicationStartInfo + config *configmodels.Config + factories map[configmodels.Type]component.ExtensionFactory +} + +// NewExportersBuilder creates a new ExportersBuilder. Call BuildExporters() on the returned value. +func NewExtensionsBuilder( + logger *zap.Logger, + appInfo component.ApplicationStartInfo, + config *configmodels.Config, + factories map[configmodels.Type]component.ExtensionFactory, +) *ExtensionsBuilder { + return &ExtensionsBuilder{logger.With(zap.String(kindLogKey, kindLogExtension)), appInfo, config, factories} +} + +// Build extensions from config. +func (eb *ExtensionsBuilder) Build() (Extensions, error) { + extensions := make(Extensions) + + for _, extName := range eb.config.Service.Extensions { + extCfg, exists := eb.config.Extensions[extName] + if !exists { + return nil, fmt.Errorf("extension %q is not configured", extName) + } + + componentLogger := eb.logger.With(zap.String(typeLogKey, string(extCfg.Type())), zap.String(nameLogKey, extCfg.Name())) + ext, err := eb.buildExtension(componentLogger, eb.appInfo, extCfg) + if err != nil { + return nil, err + } + + extensions[extCfg] = ext + } + + return extensions, nil +} + +func (eb *ExtensionsBuilder) buildExtension(logger *zap.Logger, appInfo component.ApplicationStartInfo, cfg configmodels.Extension) (*builtExtension, error) { + factory := eb.factories[cfg.Type()] + if factory == nil { + return nil, fmt.Errorf("extension factory for type %q is not configured", cfg.Type()) + } + + ext := &builtExtension{ + logger: logger, + } + + creationParams := component.ExtensionCreateParams{ + Logger: logger, + ApplicationStartInfo: appInfo, + } + + ex, err := factory.CreateExtension(context.Background(), creationParams, cfg) + if err != nil { + return nil, fmt.Errorf("failed to create extension %q: %w", cfg.Name(), err) + } + + // Check if the factory really created the extension. + if ex == nil { + return nil, fmt.Errorf("factory for %q produced a nil extension", cfg.Name()) + } + + ext.extension = ex + + return ext, nil +} diff --git a/internal/otel_collector/service/builder/pipelines_builder.go b/internal/otel_collector/service/builder/pipelines_builder.go new file mode 100644 index 00000000000..bf13b97563c --- /dev/null +++ b/internal/otel_collector/service/builder/pipelines_builder.go @@ -0,0 +1,266 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor" +) + +// builtPipeline is a pipeline that is built based on a config. +// It can have a trace and/or a metrics consumer (the consumer is either the first +// processor in the pipeline or the exporter if pipeline has no processors). +type builtPipeline struct { + logger *zap.Logger + firstTC consumer.TracesConsumer + firstMC consumer.MetricsConsumer + firstLC consumer.LogsConsumer + + // MutatesConsumedData is set to true if any processors in the pipeline + // can mutate the TraceData or MetricsData input argument. + MutatesConsumedData bool + + processors []component.Processor +} + +// BuiltPipelines is a map of build pipelines created from pipeline configs. +type BuiltPipelines map[*configmodels.Pipeline]*builtPipeline + +func (bps BuiltPipelines) StartProcessors(ctx context.Context, host component.Host) error { + for _, bp := range bps { + bp.logger.Info("Pipeline is starting...") + // Start in reverse order, starting from the back of processors pipeline. + // This is important so that processors that are earlier in the pipeline and + // reference processors that are later in the pipeline do not start sending + // data to later pipelines which are not yet started. + for i := len(bp.processors) - 1; i >= 0; i-- { + if err := bp.processors[i].Start(ctx, host); err != nil { + return err + } + } + bp.logger.Info("Pipeline is started.") + } + return nil +} + +func (bps BuiltPipelines) ShutdownProcessors(ctx context.Context) error { + var errs []error + for _, bp := range bps { + bp.logger.Info("Pipeline is shutting down...") + for _, p := range bp.processors { + if err := p.Shutdown(ctx); err != nil { + errs = append(errs, err) + } + } + bp.logger.Info("Pipeline is shutdown.") + } + + return componenterror.CombineErrors(errs) +} + +// PipelinesBuilder builds pipelines from config. +type PipelinesBuilder struct { + logger *zap.Logger + appInfo component.ApplicationStartInfo + config *configmodels.Config + exporters Exporters + factories map[configmodels.Type]component.ProcessorFactory +} + +// NewPipelinesBuilder creates a new PipelinesBuilder. Requires exporters to be already +// built via ExportersBuilder. Call BuildProcessors() on the returned value. +func NewPipelinesBuilder( + logger *zap.Logger, + appInfo component.ApplicationStartInfo, + config *configmodels.Config, + exporters Exporters, + factories map[configmodels.Type]component.ProcessorFactory, +) *PipelinesBuilder { + return &PipelinesBuilder{logger, appInfo, config, exporters, factories} +} + +// BuildProcessors pipeline processors from config. +func (pb *PipelinesBuilder) Build() (BuiltPipelines, error) { + pipelineProcessors := make(BuiltPipelines) + + for _, pipeline := range pb.config.Service.Pipelines { + firstProcessor, err := pb.buildPipeline(context.Background(), pipeline) + if err != nil { + return nil, err + } + pipelineProcessors[pipeline] = firstProcessor + } + + return pipelineProcessors, nil +} + +// Builds a pipeline of processors. Returns the first processor in the pipeline. +// The last processor in the pipeline will be plugged to fan out the data into exporters +// that are configured for this pipeline. +func (pb *PipelinesBuilder) buildPipeline(ctx context.Context, pipelineCfg *configmodels.Pipeline) (*builtPipeline, error) { + + // BuildProcessors the pipeline backwards. + + // First create a consumer junction point that fans out the data to all exporters. + var tc consumer.TracesConsumer + var mc consumer.MetricsConsumer + var lc consumer.LogsConsumer + + switch pipelineCfg.InputType { + case configmodels.TracesDataType: + tc = pb.buildFanoutExportersTraceConsumer(pipelineCfg.Exporters) + case configmodels.MetricsDataType: + mc = pb.buildFanoutExportersMetricsConsumer(pipelineCfg.Exporters) + case configmodels.LogsDataType: + lc = pb.buildFanoutExportersLogConsumer(pipelineCfg.Exporters) + } + + mutatesConsumedData := false + + processors := make([]component.Processor, len(pipelineCfg.Processors)) + + // Now build the processors backwards, starting from the last one. + // The last processor points to consumer which fans out to exporters, then + // the processor itself becomes a consumer for the one that precedes it in + // in the pipeline and so on. + for i := len(pipelineCfg.Processors) - 1; i >= 0; i-- { + procName := pipelineCfg.Processors[i] + procCfg := pb.config.Processors[procName] + + factory := pb.factories[procCfg.Type()] + + // This processor must point to the next consumer and then + // it becomes the next for the previous one (previous in the pipeline, + // which we will build in the next loop iteration). + var err error + componentLogger := pb.logger.With(zap.String(kindLogKey, kindLogsProcessor), zap.String(typeLogKey, string(procCfg.Type())), zap.String(nameLogKey, procCfg.Name())) + creationParams := component.ProcessorCreateParams{ + Logger: componentLogger, + ApplicationStartInfo: pb.appInfo, + } + + switch pipelineCfg.InputType { + case configmodels.TracesDataType: + var proc component.TracesProcessor + proc, err = factory.CreateTracesProcessor(ctx, creationParams, procCfg, tc) + if proc != nil { + mutatesConsumedData = mutatesConsumedData || proc.GetCapabilities().MutatesConsumedData + } + processors[i] = proc + tc = proc + case configmodels.MetricsDataType: + var proc component.MetricsProcessor + proc, err = factory.CreateMetricsProcessor(ctx, creationParams, procCfg, mc) + if proc != nil { + mutatesConsumedData = mutatesConsumedData || proc.GetCapabilities().MutatesConsumedData + } + processors[i] = proc + mc = proc + + case configmodels.LogsDataType: + var proc component.LogsProcessor + proc, err = factory.CreateLogsProcessor(ctx, creationParams, procCfg, lc) + if proc != nil { + mutatesConsumedData = mutatesConsumedData || proc.GetCapabilities().MutatesConsumedData + } + processors[i] = proc + lc = proc + + default: + return nil, fmt.Errorf("error creating processor %q in pipeline %q, data type %s is not supported", + procName, pipelineCfg.Name, pipelineCfg.InputType) + } + + if err != nil { + return nil, fmt.Errorf("error creating processor %q in pipeline %q: %v", + procName, pipelineCfg.Name, err) + } + + // Check if the factory really created the processor. + if tc == nil && mc == nil && lc == nil { + return nil, fmt.Errorf("factory for %q produced a nil processor", procCfg.Name()) + } + } + + pipelineLogger := pb.logger.With(zap.String("pipeline_name", pipelineCfg.Name), + zap.String("pipeline_datatype", string(pipelineCfg.InputType))) + pipelineLogger.Info("Pipeline is enabled.") + + bp := &builtPipeline{ + pipelineLogger, + tc, + mc, + lc, + mutatesConsumedData, + processors, + } + + return bp, nil +} + +// Converts the list of exporter names to a list of corresponding builtExporters. +func (pb *PipelinesBuilder) getBuiltExportersByNames(exporterNames []string) []*builtExporter { + var result []*builtExporter + for _, name := range exporterNames { + exporter := pb.exporters[pb.config.Exporters[name]] + result = append(result, exporter) + } + + return result +} + +func (pb *PipelinesBuilder) buildFanoutExportersTraceConsumer(exporterNames []string) consumer.TracesConsumer { + builtExporters := pb.getBuiltExportersByNames(exporterNames) + + var exporters []consumer.TracesConsumer + for _, builtExp := range builtExporters { + exporters = append(exporters, builtExp.getTraceExporter()) + } + + // Create a junction point that fans out to all exporters. + return processor.NewTracesFanOutConnector(exporters) +} + +func (pb *PipelinesBuilder) buildFanoutExportersMetricsConsumer(exporterNames []string) consumer.MetricsConsumer { + builtExporters := pb.getBuiltExportersByNames(exporterNames) + + var exporters []consumer.MetricsConsumer + for _, builtExp := range builtExporters { + exporters = append(exporters, builtExp.getMetricExporter()) + } + + // Create a junction point that fans out to all exporters. + return processor.NewMetricsFanOutConnector(exporters) +} + +func (pb *PipelinesBuilder) buildFanoutExportersLogConsumer(exporterNames []string) consumer.LogsConsumer { + builtExporters := pb.getBuiltExportersByNames(exporterNames) + + exporters := make([]consumer.LogsConsumer, len(builtExporters)) + for i, builtExp := range builtExporters { + exporters[i] = builtExp.getLogExporter() + } + + // Create a junction point that fans out to all exporters. + return processor.NewLogsFanOutConnector(exporters) +} diff --git a/internal/otel_collector/service/builder/pipelines_builder_test.go b/internal/otel_collector/service/builder/pipelines_builder_test.go new file mode 100644 index 00000000000..c8adfb2da3a --- /dev/null +++ b/internal/otel_collector/service/builder/pipelines_builder_test.go @@ -0,0 +1,309 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +func TestPipelinesBuilder_Build(t *testing.T) { + tests := []struct { + name string + pipelineName string + exporterNames []string + }{ + { + name: "one-exporter", + pipelineName: "traces", + exporterNames: []string{"exampleexporter"}, + }, + { + name: "multi-exporter", + pipelineName: "traces/2", + exporterNames: []string{"exampleexporter", "exampleexporter/2"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testPipeline(t, test.pipelineName, test.exporterNames) + }) + } +} + +func createExampleFactories() component.Factories { + exampleReceiverFactory := &componenttest.ExampleReceiverFactory{} + exampleProcessorFactory := &componenttest.ExampleProcessorFactory{} + exampleExporterFactory := &componenttest.ExampleExporterFactory{} + + factories := component.Factories{ + Receivers: map[configmodels.Type]component.ReceiverFactory{ + exampleReceiverFactory.Type(): exampleReceiverFactory, + }, + Processors: map[configmodels.Type]component.ProcessorFactory{ + exampleProcessorFactory.Type(): exampleProcessorFactory, + }, + Exporters: map[configmodels.Type]component.ExporterFactory{ + exampleExporterFactory.Type(): exampleExporterFactory, + }, + } + + return factories +} + +func createExampleConfig(dataType string) *configmodels.Config { + + exampleReceiverFactory := &componenttest.ExampleReceiverFactory{} + exampleProcessorFactory := &componenttest.ExampleProcessorFactory{} + exampleExporterFactory := &componenttest.ExampleExporterFactory{} + + cfg := &configmodels.Config{ + Receivers: map[string]configmodels.Receiver{ + string(exampleReceiverFactory.Type()): exampleReceiverFactory.CreateDefaultConfig(), + }, + Processors: map[string]configmodels.Processor{ + string(exampleProcessorFactory.Type()): exampleProcessorFactory.CreateDefaultConfig(), + }, + Exporters: map[string]configmodels.Exporter{ + string(exampleExporterFactory.Type()): exampleExporterFactory.CreateDefaultConfig(), + }, + Service: configmodels.Service{ + Pipelines: map[string]*configmodels.Pipeline{ + dataType: { + Name: dataType, + InputType: configmodels.DataType(dataType), + Receivers: []string{string(exampleReceiverFactory.Type())}, + Processors: []string{string(exampleProcessorFactory.Type())}, + Exporters: []string{string(exampleExporterFactory.Type())}, + }, + }, + }, + } + return cfg +} + +func TestPipelinesBuilder_BuildVarious(t *testing.T) { + + factories := createExampleFactories() + + tests := []struct { + dataType string + shouldFail bool + }{ + { + dataType: "logs", + shouldFail: false, + }, + { + dataType: "nosuchdatatype", + shouldFail: true, + }, + } + + for _, test := range tests { + t.Run(test.dataType, func(t *testing.T) { + dataType := test.dataType + + cfg := createExampleConfig(dataType) + + // BuildProcessors the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + if test.shouldFail { + assert.Error(t, err) + return + } + + require.NoError(t, err) + require.EqualValues(t, 1, len(allExporters)) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + + assert.NoError(t, err) + require.NotNil(t, pipelineProcessors) + + err = pipelineProcessors.StartProcessors(context.Background(), componenttest.NewNopHost()) + assert.NoError(t, err) + + pipelineName := dataType + processor := pipelineProcessors[cfg.Service.Pipelines[pipelineName]] + + // Ensure pipeline has its fields correctly populated. + require.NotNil(t, processor) + assert.Nil(t, processor.firstTC) + assert.Nil(t, processor.firstMC) + assert.NotNil(t, processor.firstLC) + + // Compose the list of created exporters. + exporterNames := []string{"exampleexporter"} + var exporters []*builtExporter + for _, name := range exporterNames { + // Ensure exporter is created. + exp := allExporters[cfg.Exporters[name]] + require.NotNil(t, exp) + exporters = append(exporters, exp) + } + + // Send Logs via processor and verify that all exporters of the pipeline receive it. + + // First check that there are no logs in the exporters yet. + var exporterConsumers []*componenttest.ExampleExporterConsumer + for _, exporter := range exporters { + consumer := exporter.getLogExporter().(*componenttest.ExampleExporterConsumer) + exporterConsumers = append(exporterConsumers, consumer) + require.Equal(t, len(consumer.Logs), 0) + } + + // Send one custom data. + log := pdata.Logs{} + processor.firstLC.(consumer.LogsConsumer).ConsumeLogs(context.Background(), log) + + // Now verify received data. + for _, consumer := range exporterConsumers { + // Check that the trace is received by exporter. + require.Equal(t, 1, len(consumer.Logs)) + + // Verify that span is successfully delivered. + assert.EqualValues(t, log, consumer.Logs[0]) + } + + err = pipelineProcessors.ShutdownProcessors(context.Background()) + assert.NoError(t, err) + }) + } +} + +func testPipeline(t *testing.T, pipelineName string, exporterNames []string) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + cfg, err := configtest.LoadConfigFile(t, "testdata/pipelines_builder.yaml", factories) + // Load the config + require.Nil(t, err) + + // BuildProcessors the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NoError(t, err) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + + assert.NoError(t, err) + require.NotNil(t, pipelineProcessors) + + assert.NoError(t, pipelineProcessors.StartProcessors(context.Background(), componenttest.NewNopHost())) + + processor := pipelineProcessors[cfg.Service.Pipelines[pipelineName]] + + // Ensure pipeline has its fields correctly populated. + require.NotNil(t, processor) + assert.NotNil(t, processor.firstTC) + assert.Nil(t, processor.firstMC) + + // Compose the list of created exporters. + var exporters []*builtExporter + for _, name := range exporterNames { + // Ensure exporter is created. + exp := allExporters[cfg.Exporters[name]] + require.NotNil(t, exp) + exporters = append(exporters, exp) + } + + // Send TraceData via processor and verify that all exporters of the pipeline receive it. + + // First check that there are no traces in the exporters yet. + var exporterConsumers []*componenttest.ExampleExporterConsumer + for _, exporter := range exporters { + consumer := exporter.getTraceExporter().(*componenttest.ExampleExporterConsumer) + exporterConsumers = append(exporterConsumers, consumer) + require.Equal(t, len(consumer.Traces), 0) + } + + td := testdata.GenerateTraceDataOneSpan() + processor.firstTC.(consumer.TracesConsumer).ConsumeTraces(context.Background(), td) + + // Now verify received data. + for _, consumer := range exporterConsumers { + // Check that the trace is received by exporter. + require.Equal(t, 1, len(consumer.Traces)) + + // Verify that span is successfully delivered. + assert.EqualValues(t, td, consumer.Traces[0]) + } + + err = pipelineProcessors.ShutdownProcessors(context.Background()) + assert.NoError(t, err) +} + +func TestProcessorsBuilder_ErrorOnUnsupportedProcessor(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + bf := newBadProcessorFactory() + factories.Processors[bf.Type()] = bf + + cfg, err := configtest.LoadConfigFile(t, "testdata/bad_processor_factory.yaml", factories) + require.Nil(t, err) + + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NoError(t, err) + + // First test only trace receivers by removing the metrics pipeline. + metricsPipeline := cfg.Service.Pipelines["metrics"] + logsPipeline := cfg.Service.Pipelines["logs"] + delete(cfg.Service.Pipelines, "metrics") + delete(cfg.Service.Pipelines, "logs") + require.Equal(t, 1, len(cfg.Service.Pipelines)) + + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.Error(t, err) + assert.Zero(t, len(pipelineProcessors)) + + // Now test the metric pipeline. + delete(cfg.Service.Pipelines, "traces") + cfg.Service.Pipelines["metrics"] = metricsPipeline + require.Equal(t, 1, len(cfg.Service.Pipelines)) + + pipelineProcessors, err = NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.Error(t, err) + assert.Zero(t, len(pipelineProcessors)) + + // Now test the logs pipeline. + delete(cfg.Service.Pipelines, "metrics") + cfg.Service.Pipelines["logs"] = logsPipeline + require.Equal(t, 1, len(cfg.Service.Pipelines)) + + pipelineProcessors, err = NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.Error(t, err) + assert.Zero(t, len(pipelineProcessors)) +} + +func newBadProcessorFactory() component.ProcessorFactory { + return processorhelper.NewFactory("bf", func() configmodels.Processor { + return &configmodels.ProcessorSettings{ + TypeVal: "bf", + NameVal: "bf", + } + }) +} diff --git a/internal/otel_collector/service/builder/receivers_builder.go b/internal/otel_collector/service/builder/receivers_builder.go new file mode 100644 index 00000000000..48724359f07 --- /dev/null +++ b/internal/otel_collector/service/builder/receivers_builder.go @@ -0,0 +1,352 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "errors" + "fmt" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config/configerror" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor" +) + +var errUnusedReceiver = errors.New("receiver defined but not used by any pipeline") + +// builtReceiver is a receiver that is built based on a config. It can have +// a trace and/or a metrics component. +type builtReceiver struct { + logger *zap.Logger + receiver component.Receiver +} + +// Start the receiver. +func (rcv *builtReceiver) Start(ctx context.Context, host component.Host) error { + return rcv.receiver.Start(ctx, host) +} + +// Stop the receiver. +func (rcv *builtReceiver) Shutdown(ctx context.Context) error { + return rcv.receiver.Shutdown(ctx) +} + +// Receivers is a map of receivers created from receiver configs. +type Receivers map[configmodels.Receiver]*builtReceiver + +// StopAll stops all receivers. +func (rcvs Receivers) ShutdownAll(ctx context.Context) error { + var errs []error + for _, rcv := range rcvs { + err := rcv.Shutdown(ctx) + if err != nil { + errs = append(errs, err) + } + } + + return componenterror.CombineErrors(errs) +} + +// StartAll starts all receivers. +func (rcvs Receivers) StartAll(ctx context.Context, host component.Host) error { + for _, rcv := range rcvs { + rcv.logger.Info("Receiver is starting...") + + if err := rcv.Start(ctx, host); err != nil { + return err + } + rcv.logger.Info("Receiver started.") + } + return nil +} + +// ReceiversBuilder builds receivers from config. +type ReceiversBuilder struct { + logger *zap.Logger + appInfo component.ApplicationStartInfo + config *configmodels.Config + builtPipelines BuiltPipelines + factories map[configmodels.Type]component.ReceiverFactory +} + +// NewReceiversBuilder creates a new ReceiversBuilder. Call BuildProcessors() on the returned value. +func NewReceiversBuilder( + logger *zap.Logger, + appInfo component.ApplicationStartInfo, + config *configmodels.Config, + builtPipelines BuiltPipelines, + factories map[configmodels.Type]component.ReceiverFactory, +) *ReceiversBuilder { + return &ReceiversBuilder{logger.With(zap.String(kindLogKey, kindLogsReceiver)), appInfo, config, builtPipelines, factories} +} + +// BuildProcessors receivers from config. +func (rb *ReceiversBuilder) Build() (Receivers, error) { + receivers := make(Receivers) + + // BuildProcessors receivers based on configuration. + for _, cfg := range rb.config.Receivers { + logger := rb.logger.With(zap.String(typeLogKey, string(cfg.Type())), zap.String(nameLogKey, cfg.Name())) + rcv, err := rb.buildReceiver(context.Background(), logger, rb.appInfo, cfg) + if err != nil { + if err == errUnusedReceiver { + logger.Info("Ignoring receiver as it is not used by any pipeline", zap.String("receiver", cfg.Name())) + continue + } + return nil, err + } + receivers[cfg] = rcv + } + + return receivers, nil +} + +// hasReceiver returns true if the pipeline is attached to specified receiver. +func hasReceiver(pipeline *configmodels.Pipeline, receiverName string) bool { + for _, name := range pipeline.Receivers { + if name == receiverName { + return true + } + } + return false +} + +type attachedPipelines map[configmodels.DataType][]*builtPipeline + +func (rb *ReceiversBuilder) findPipelinesToAttach(config configmodels.Receiver) (attachedPipelines, error) { + // A receiver may be attached to multiple pipelines. Pipelines may consume different + // data types. We need to compile the list of pipelines of each type that must be + // attached to this receiver according to configuration. + + pipelinesToAttach := make(attachedPipelines) + pipelinesToAttach[configmodels.TracesDataType] = make([]*builtPipeline, 0) + pipelinesToAttach[configmodels.MetricsDataType] = make([]*builtPipeline, 0) + + // Iterate over all pipelines. + for _, pipelineCfg := range rb.config.Service.Pipelines { + // Get the first processor of the pipeline. + pipelineProcessor := rb.builtPipelines[pipelineCfg] + if pipelineProcessor == nil { + return nil, fmt.Errorf("cannot find pipeline processor for pipeline %s", + pipelineCfg.Name) + } + + // Is this receiver attached to the pipeline? + if hasReceiver(pipelineCfg, config.Name()) { + if _, exists := pipelinesToAttach[pipelineCfg.InputType]; !exists { + pipelinesToAttach[pipelineCfg.InputType] = make([]*builtPipeline, 0) + } + + // Yes, add it to the list of pipelines of corresponding data type. + pipelinesToAttach[pipelineCfg.InputType] = + append(pipelinesToAttach[pipelineCfg.InputType], pipelineProcessor) + } + } + + return pipelinesToAttach, nil +} + +func (rb *ReceiversBuilder) attachReceiverToPipelines( + ctx context.Context, + logger *zap.Logger, + appInfo component.ApplicationStartInfo, + factory component.ReceiverFactory, + dataType configmodels.DataType, + config configmodels.Receiver, + rcv *builtReceiver, + builtPipelines []*builtPipeline, +) error { + // There are pipelines of the specified data type that must be attached to + // the receiver. Create the receiver of corresponding data type and make + // sure its output is fanned out to all attached pipelines. + var err error + var createdReceiver component.Receiver + creationParams := component.ReceiverCreateParams{ + Logger: logger, + ApplicationStartInfo: appInfo, + } + + switch dataType { + case configmodels.TracesDataType: + junction := buildFanoutTraceConsumer(builtPipelines) + createdReceiver, err = factory.CreateTracesReceiver(ctx, creationParams, config, junction) + + case configmodels.MetricsDataType: + junction := buildFanoutMetricConsumer(builtPipelines) + createdReceiver, err = factory.CreateMetricsReceiver(ctx, creationParams, config, junction) + + case configmodels.LogsDataType: + junction := buildFanoutLogConsumer(builtPipelines) + createdReceiver, err = factory.CreateLogsReceiver(ctx, creationParams, config, junction) + + default: + err = configerror.ErrDataTypeIsNotSupported + } + + if err != nil { + if err == configerror.ErrDataTypeIsNotSupported { + return fmt.Errorf( + "receiver %s does not support %s but it was used in a "+ + "%s pipeline", + config.Name(), + dataType, + dataType) + } + return fmt.Errorf("cannot create receiver %s: %s", config.Name(), err.Error()) + } + + // Check if the factory really created the receiver. + if createdReceiver == nil { + return fmt.Errorf("factory for %q produced a nil receiver", config.Name()) + } + + if rcv.receiver != nil { + // The receiver was previously created for this config. This can happen if the + // same receiver type supports more than one data type. In that case we expect + // that CreateTracesReceiver and CreateMetricsReceiver return the same value. + if rcv.receiver != createdReceiver { + return fmt.Errorf( + "factory for %q is implemented incorrectly: "+ + "CreateTracesReceiver and CreateMetricsReceiver must return the same "+ + "receiver pointer when creating receivers of different data types", + config.Name(), + ) + } + } + rcv.receiver = createdReceiver + + logger.Info("Receiver is enabled.", zap.String("datatype", string(dataType))) + + return nil +} + +func (rb *ReceiversBuilder) buildReceiver(ctx context.Context, logger *zap.Logger, appInfo component.ApplicationStartInfo, config configmodels.Receiver) (*builtReceiver, error) { + + // First find pipelines that must be attached to this receiver. + pipelinesToAttach, err := rb.findPipelinesToAttach(config) + if err != nil { + return nil, err + } + + // Prepare to build the receiver. + factory := rb.factories[config.Type()] + if factory == nil { + return nil, fmt.Errorf("receiver factory not found for type: %s", config.Type()) + } + rcv := &builtReceiver{ + logger: logger, + } + + // Now we have list of pipelines broken down by data type. Iterate for each data type. + for dataType, pipelines := range pipelinesToAttach { + if len(pipelines) == 0 { + // No pipelines of this data type are attached to this receiver. + continue + } + + // Attach the corresponding part of the receiver to all pipelines that require + // this data type. + err := rb.attachReceiverToPipelines(ctx, logger, appInfo, factory, dataType, config, rcv, pipelines) + if err != nil { + return nil, err + } + } + + if rcv.receiver == nil { + return nil, errUnusedReceiver + } + + return rcv, nil +} + +func buildFanoutTraceConsumer(pipelines []*builtPipeline) consumer.TracesConsumer { + // Optimize for the case when there is only one processor, no need to create junction point. + if len(pipelines) == 1 { + return pipelines[0].firstTC + } + + var pipelineConsumers []consumer.TracesConsumer + anyPipelineMutatesData := false + for _, pipeline := range pipelines { + pipelineConsumers = append(pipelineConsumers, pipeline.firstTC) + anyPipelineMutatesData = anyPipelineMutatesData || pipeline.MutatesConsumedData + } + + // Create a junction point that fans out to all pipelines. + if anyPipelineMutatesData { + // If any pipeline mutates data use a cloning fan out connector + // so that it is safe to modify fanned out data. + // TODO: if there are more than 2 pipelines only clone data for pipelines that + // declare the intent to mutate the data. Pipelines that do not mutate the data + // can consume shared data. + return processor.NewTracesCloningFanOutConnector(pipelineConsumers) + } + return processor.NewTracesFanOutConnector(pipelineConsumers) +} + +func buildFanoutMetricConsumer(pipelines []*builtPipeline) consumer.MetricsConsumer { + // Optimize for the case when there is only one processor, no need to create junction point. + if len(pipelines) == 1 { + return pipelines[0].firstMC + } + + var pipelineConsumers []consumer.MetricsConsumer + anyPipelineMutatesData := false + for _, pipeline := range pipelines { + pipelineConsumers = append(pipelineConsumers, pipeline.firstMC) + anyPipelineMutatesData = anyPipelineMutatesData || pipeline.MutatesConsumedData + } + + // Create a junction point that fans out to all pipelines. + if anyPipelineMutatesData { + // If any pipeline mutates data use a cloning fan out connector + // so that it is safe to modify fanned out data. + // TODO: if there are more than 2 pipelines only clone data for pipelines that + // declare the intent to mutate the data. Pipelines that do not mutate the data + // can consume shared data. + return processor.NewMetricsCloningFanOutConnector(pipelineConsumers) + } + return processor.NewMetricsFanOutConnector(pipelineConsumers) +} + +func buildFanoutLogConsumer(pipelines []*builtPipeline) consumer.LogsConsumer { + // Optimize for the case when there is only one processor, no need to create junction point. + if len(pipelines) == 1 { + return pipelines[0].firstLC + } + + var pipelineConsumers []consumer.LogsConsumer + anyPipelineMutatesData := false + for _, pipeline := range pipelines { + pipelineConsumers = append(pipelineConsumers, pipeline.firstLC) + anyPipelineMutatesData = anyPipelineMutatesData || pipeline.MutatesConsumedData + } + + // Create a junction point that fans out to all pipelines. + if anyPipelineMutatesData { + // If any pipeline mutates data use a cloning fan out connector + // so that it is safe to modify fanned out data. + // TODO: if there are more than 2 pipelines only clone data for pipelines that + // declare the intent to mutate the data. Pipelines that do not mutate the data + // can consume shared data. + return processor.NewLogsCloningFanOutConnector(pipelineConsumers) + } + return processor.NewLogsFanOutConnector(pipelineConsumers) +} diff --git a/internal/otel_collector/service/builder/receivers_builder_test.go b/internal/otel_collector/service/builder/receivers_builder_test.go new file mode 100644 index 00000000000..e23e0b47317 --- /dev/null +++ b/internal/otel_collector/service/builder/receivers_builder_test.go @@ -0,0 +1,396 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/processor/attributesprocessor" + "go.opentelemetry.io/collector/receiver/receiverhelper" +) + +type testCase struct { + name string + receiverName string + exporterNames []string + spanDuplicationByExporter map[string]int + hasTraces bool + hasMetrics bool +} + +func TestReceiversBuilder_Build(t *testing.T) { + tests := []testCase{ + { + name: "one-exporter", + receiverName: "examplereceiver", + exporterNames: []string{"exampleexporter"}, + hasTraces: true, + hasMetrics: true, + }, + { + name: "multi-exporter", + receiverName: "examplereceiver/2", + exporterNames: []string{"exampleexporter", "exampleexporter/2"}, + hasTraces: true, + }, + { + name: "multi-metrics-receiver", + receiverName: "examplereceiver/3", + exporterNames: []string{"exampleexporter", "exampleexporter/2"}, + hasTraces: false, + hasMetrics: true, + }, + { + name: "multi-receiver-multi-exporter", + receiverName: "examplereceiver/multi", + exporterNames: []string{"exampleexporter", "exampleexporter/2"}, + + // Check pipelines_builder.yaml to understand this case. + // We have 2 pipelines, one exporting to one exporter, the other + // exporting to both exporters, so we expect a duplication on + // one of the exporters, but not on the other. + spanDuplicationByExporter: map[string]int{ + "exampleexporter": 2, "exampleexporter/2": 1, + }, + hasTraces: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testReceivers(t, test) + }) + } +} + +func testReceivers( + t *testing.T, + test testCase, +) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + attrFactory := attributesprocessor.NewFactory() + factories.Processors[attrFactory.Type()] = attrFactory + cfg, err := configtest.LoadConfigFile(t, "testdata/pipelines_builder.yaml", factories) + require.Nil(t, err) + + // Build the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NoError(t, err) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.NoError(t, err) + receivers, err := NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + + assert.NoError(t, err) + require.NotNil(t, receivers) + + receiver := receivers[cfg.Receivers[test.receiverName]] + + // Ensure receiver has its fields correctly populated. + require.NotNil(t, receiver) + + assert.NotNil(t, receiver.receiver) + + // Compose the list of created exporters. + var exporters []*builtExporter + for _, name := range test.exporterNames { + // Ensure exporter is created. + exp := allExporters[cfg.Exporters[name]] + require.NotNil(t, exp) + exporters = append(exporters, exp) + } + + // Send TraceData via receiver and verify that all exporters of the pipeline receive it. + + // First check that there are no traces in the exporters yet. + for _, exporter := range exporters { + consumer := exporter.getTraceExporter().(*componenttest.ExampleExporterConsumer) + require.Equal(t, len(consumer.Traces), 0) + require.Equal(t, len(consumer.Metrics), 0) + } + + if test.hasTraces { + traceProducer := receiver.receiver.(*componenttest.ExampleReceiverProducer) + traceProducer.TraceConsumer.ConsumeTraces(context.Background(), testdata.GenerateTraceDataOneSpan()) + } + + metrics := testdata.GenerateMetricsOneMetric() + if test.hasMetrics { + metricsProducer := receiver.receiver.(*componenttest.ExampleReceiverProducer) + metricsProducer.MetricsConsumer.ConsumeMetrics(context.Background(), metrics) + } + + // Now verify received data. + for _, name := range test.exporterNames { + // Check that the data is received by exporter. + exporter := allExporters[cfg.Exporters[name]] + + // Validate traces. + if test.hasTraces { + var spanDuplicationCount int + if test.spanDuplicationByExporter != nil { + spanDuplicationCount = test.spanDuplicationByExporter[name] + } else { + spanDuplicationCount = 1 + } + + traceConsumer := exporter.getTraceExporter().(*componenttest.ExampleExporterConsumer) + require.Equal(t, spanDuplicationCount, len(traceConsumer.Traces)) + + for i := 0; i < spanDuplicationCount; i++ { + assert.EqualValues(t, testdata.GenerateTraceDataOneSpan(), traceConsumer.Traces[i]) + } + } + + // Validate metrics. + if test.hasMetrics { + metricsConsumer := exporter.getMetricExporter().(*componenttest.ExampleExporterConsumer) + require.Equal(t, 1, len(metricsConsumer.Metrics)) + assert.EqualValues(t, metrics, metricsConsumer.Metrics[0]) + } + } +} + +func TestReceiversBuilder_BuildCustom(t *testing.T) { + factories := createExampleFactories() + + tests := []struct { + dataType string + shouldFail bool + }{ + { + dataType: "logs", + shouldFail: false, + }, + { + dataType: "nosuchdatatype", + shouldFail: true, + }, + } + + for _, test := range tests { + t.Run(test.dataType, func(t *testing.T) { + dataType := test.dataType + + cfg := createExampleConfig(dataType) + + // Build the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + if test.shouldFail { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.NoError(t, err) + receivers, err := NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + + assert.NoError(t, err) + require.NotNil(t, receivers) + + receiver := receivers[cfg.Receivers["examplereceiver"]] + + // Ensure receiver has its fields correctly populated. + require.NotNil(t, receiver) + + assert.NotNil(t, receiver.receiver) + + // Compose the list of created exporters. + exporterNames := []string{"exampleexporter"} + var exporters []*builtExporter + for _, name := range exporterNames { + // Ensure exporter is created. + exp := allExporters[cfg.Exporters[name]] + require.NotNil(t, exp) + exporters = append(exporters, exp) + } + + // Send Data via receiver and verify that all exporters of the pipeline receive it. + + // First check that there are no traces in the exporters yet. + for _, exporter := range exporters { + consumer := exporter.getLogExporter().(*componenttest.ExampleExporterConsumer) + require.Equal(t, len(consumer.Logs), 0) + } + + // Send one data. + log := pdata.Logs{} + producer := receiver.receiver.(*componenttest.ExampleReceiverProducer) + producer.LogConsumer.ConsumeLogs(context.Background(), log) + + // Now verify received data. + for _, name := range exporterNames { + // Check that the data is received by exporter. + exporter := allExporters[cfg.Exporters[name]] + + // Validate exported data. + consumer := exporter.getLogExporter().(*componenttest.ExampleExporterConsumer) + require.Equal(t, 1, len(consumer.Logs)) + assert.EqualValues(t, log, consumer.Logs[0]) + } + }) + } +} + +func TestReceiversBuilder_DataTypeError(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + attrFactory := attributesprocessor.NewFactory() + factories.Processors[attrFactory.Type()] = attrFactory + cfg, err := configtest.LoadConfigFile(t, "testdata/pipelines_builder.yaml", factories) + assert.NoError(t, err) + + // Make examplereceiver to "unsupport" trace data type. + receiver := cfg.Receivers["examplereceiver"] + receiver.(*componenttest.ExampleReceiver).FailTraceCreation = true + + // Build the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NoError(t, err) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.NoError(t, err) + receivers, err := NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + + // This should fail because "examplereceiver" is attached to "traces" pipeline + // which is a configuration error. + assert.NotNil(t, err) + assert.Nil(t, receivers) +} + +func TestReceiversBuilder_StartAll(t *testing.T) { + receivers := make(Receivers) + rcvCfg := &configmodels.ReceiverSettings{} + + receiver := &componenttest.ExampleReceiverProducer{} + + receivers[rcvCfg] = &builtReceiver{ + logger: zap.NewNop(), + receiver: receiver, + } + + assert.False(t, receiver.Started) + + err := receivers.StartAll(context.Background(), componenttest.NewNopHost()) + assert.NoError(t, err) + + assert.True(t, receiver.Started) +} + +func TestReceiversBuilder_StopAll(t *testing.T) { + receivers := make(Receivers) + rcvCfg := &configmodels.ReceiverSettings{} + + receiver := &componenttest.ExampleReceiverProducer{} + + receivers[rcvCfg] = &builtReceiver{ + logger: zap.NewNop(), + receiver: receiver, + } + + assert.False(t, receiver.Stopped) + + assert.NoError(t, receivers.ShutdownAll(context.Background())) + + assert.True(t, receiver.Stopped) +} + +func TestReceiversBuilder_ErrorOnNilReceiver(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + bf := newBadReceiverFactory() + factories.Receivers[bf.Type()] = bf + + cfg, err := configtest.LoadConfigFile(t, "testdata/bad_receiver_factory.yaml", factories) + require.Nil(t, err) + + // Build the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NoError(t, err) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.NoError(t, err) + + // First test only trace receivers by removing the metrics pipeline. + metricsPipeline := cfg.Service.Pipelines["metrics"] + logsPipeline := cfg.Service.Pipelines["logs"] + delete(cfg.Service.Pipelines, "metrics") + delete(cfg.Service.Pipelines, "logs") + require.Equal(t, 1, len(cfg.Service.Pipelines)) + + receivers, err := NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + assert.Error(t, err) + assert.Zero(t, len(receivers)) + + // Now test the metric pipeline. + delete(cfg.Service.Pipelines, "traces") + cfg.Service.Pipelines["metrics"] = metricsPipeline + require.Equal(t, 1, len(cfg.Service.Pipelines)) + + receivers, err = NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + assert.Error(t, err) + assert.Zero(t, len(receivers)) + + // Now test the metric pipeline. + delete(cfg.Service.Pipelines, "metrics") + cfg.Service.Pipelines["logs"] = logsPipeline + require.Equal(t, 1, len(cfg.Service.Pipelines)) + + receivers, err = NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + assert.Error(t, err) + assert.Zero(t, len(receivers)) +} + +func TestReceiversBuilder_Unused(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + cfg, err := configtest.LoadConfigFile(t, "testdata/unused_receiver.yaml", factories) + assert.NoError(t, err) + + // Build the pipeline + allExporters, err := NewExportersBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, factories.Exporters).Build() + assert.NoError(t, err) + pipelineProcessors, err := NewPipelinesBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, allExporters, factories.Processors).Build() + assert.NoError(t, err) + receivers, err := NewReceiversBuilder(zap.NewNop(), componenttest.TestApplicationStartInfo(), cfg, pipelineProcessors, factories.Receivers).Build() + assert.NoError(t, err) + assert.NotNil(t, receivers) + + assert.NoError(t, receivers.StartAll(context.Background(), componenttest.NewNopHost())) + assert.NoError(t, receivers.ShutdownAll(context.Background())) +} + +func newBadReceiverFactory() component.ReceiverFactory { + return receiverhelper.NewFactory("bf", func() configmodels.Receiver { + return &configmodels.ReceiverSettings{ + TypeVal: "bf", + NameVal: "bf", + } + }) +} diff --git a/internal/otel_collector/service/builder/testdata/bad_processor_factory.yaml b/internal/otel_collector/service/builder/testdata/bad_processor_factory.yaml new file mode 100644 index 00000000000..0a1deac5c56 --- /dev/null +++ b/internal/otel_collector/service/builder/testdata/bad_processor_factory.yaml @@ -0,0 +1,23 @@ +receivers: + examplereceiver: +processors: + bf/traces: # this is the bad processor factory + bf/metrics: + bf/logs: +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + processors: [bf/traces] + exporters: [exampleexporter] + metrics: + receivers: [examplereceiver] + processors: [bf/metrics] + exporters: [exampleexporter] + logs: + receivers: [examplereceiver] + processors: [bf/logs] + exporters: [exampleexporter] diff --git a/internal/otel_collector/service/builder/testdata/bad_receiver_factory.yaml b/internal/otel_collector/service/builder/testdata/bad_receiver_factory.yaml new file mode 100644 index 00000000000..3a6c5cecbc2 --- /dev/null +++ b/internal/otel_collector/service/builder/testdata/bad_receiver_factory.yaml @@ -0,0 +1,16 @@ +receivers: + bf: # this is the bad receiver factory +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [bf] + exporters: [exampleexporter] + metrics: + receivers: [bf] + exporters: [exampleexporter] + logs: + receivers: [bf] + exporters: [exampleexporter] \ No newline at end of file diff --git a/internal/otel_collector/service/builder/testdata/pipelines_builder.yaml b/internal/otel_collector/service/builder/testdata/pipelines_builder.yaml new file mode 100644 index 00000000000..ef965adb5fa --- /dev/null +++ b/internal/otel_collector/service/builder/testdata/pipelines_builder.yaml @@ -0,0 +1,40 @@ +receivers: + examplereceiver: + examplereceiver/2: + examplereceiver/3: + examplereceiver/multi: + +processors: + exampleprocessor: + +exporters: + exampleexporter: + exampleexporter/2: + +service: + pipelines: + traces: + receivers: [examplereceiver, examplereceiver/multi] + processors: [exampleprocessor] + exporters: [exampleexporter] + + traces/2: + receivers: [examplereceiver/2, examplereceiver/multi] + processors: [exampleprocessor] + exporters: [exampleexporter, exampleexporter/2] + + metrics: + receivers: [examplereceiver] + exporters: [exampleexporter] + + metrics/2: + receivers: [examplereceiver/3] + exporters: [exampleexporter] + + metrics/3: + receivers: [examplereceiver/3] + exporters: [exampleexporter/2] + + logs: + receivers: [examplereceiver/3] + exporters: [exampleexporter/2] diff --git a/internal/otel_collector/service/builder/testdata/unused_receiver.yaml b/internal/otel_collector/service/builder/testdata/unused_receiver.yaml new file mode 100644 index 00000000000..4465c1e39c9 --- /dev/null +++ b/internal/otel_collector/service/builder/testdata/unused_receiver.yaml @@ -0,0 +1,12 @@ +receivers: + examplereceiver: + multireceiver: +processors: +exporters: + exampleexporter: + +service: + pipelines: + traces: + receivers: [examplereceiver] + exporters: [exampleexporter] \ No newline at end of file diff --git a/internal/otel_collector/service/defaultcomponents/defaults.go b/internal/otel_collector/service/defaultcomponents/defaults.go new file mode 100644 index 00000000000..23dbabfbd49 --- /dev/null +++ b/internal/otel_collector/service/defaultcomponents/defaults.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package defaultcomponents composes the default set of components used by the otel service +package defaultcomponents + +import ( + "go.opentelemetry.io/collector/component" +) + +// Components returns the default set of components used by the +// OpenTelemetry collector. +func Components() (component.Factories, error) { + return component.Factories{}, nil +} diff --git a/internal/otel_collector/service/defaultcomponents/defaults_test.go b/internal/otel_collector/service/defaultcomponents/defaults_test.go new file mode 100644 index 00000000000..8284a07ad5f --- /dev/null +++ b/internal/otel_collector/service/defaultcomponents/defaults_test.go @@ -0,0 +1,105 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Program otelcol is the OpenTelemetry Collector that collects stats +// and traces and exports to a configured backend. +package defaultcomponents + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/config/configmodels" +) + +func TestDefaultComponents(t *testing.T) { + expectedExtensions := []configmodels.Type{ + "health_check", + "pprof", + "zpages", + "fluentbit", + } + expectedReceivers := []configmodels.Type{ + "jaeger", + "zipkin", + "prometheus", + "opencensus", + "otlp", + "hostmetrics", + "fluentforward", + "kafka", + } + expectedProcessors := []configmodels.Type{ + "attributes", + "resource", + "queued_retry", + "batch", + "memory_limiter", + "probabilistic_sampler", + "span", + "filter", + } + expectedExporters := []configmodels.Type{ + "opencensus", + "prometheus", + "prometheusremotewrite", + "logging", + "zipkin", + "jaeger", + "file", + "otlp", + "otlphttp", + "kafka", + } + + factories, err := Components() + assert.NoError(t, err) + + exts := factories.Extensions + assert.Equal(t, len(expectedExtensions), len(exts)) + for _, k := range expectedExtensions { + v, ok := exts[k] + assert.True(t, ok) + assert.Equal(t, k, v.Type()) + } + + recvs := factories.Receivers + assert.Equal(t, len(expectedReceivers), len(recvs)) + for _, k := range expectedReceivers { + v, ok := recvs[k] + require.True(t, ok) + assert.Equal(t, k, v.Type()) + assert.Equal(t, k, v.CreateDefaultConfig().Type()) + } + + procs := factories.Processors + assert.Equal(t, len(expectedProcessors), len(procs)) + for _, k := range expectedProcessors { + v, ok := procs[k] + require.True(t, ok) + assert.Equal(t, k, v.Type()) + assert.Equal(t, k, v.CreateDefaultConfig().Type()) + } + + exps := factories.Exporters + assert.Equal(t, len(expectedExporters), len(exps)) + for _, k := range expectedExporters { + v, ok := exps[k] + require.True(t, ok) + assert.Equal(t, k, v.Type()) + assert.Equal(t, k, v.CreateDefaultConfig().Type()) + } +} diff --git a/internal/otel_collector/service/defaultcomponents/docs_test.go b/internal/otel_collector/service/defaultcomponents/docs_test.go new file mode 100644 index 00000000000..79dfaf2dc48 --- /dev/null +++ b/internal/otel_collector/service/defaultcomponents/docs_test.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package defaultcomponents + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component/componenttest" +) + +const ( + relativeDefaultComponentsPath = "service/defaultcomponents/defaults.go" + projectGoModule = "go.opentelemetry.io/collector" +) + +// TestComponentDocs verifies existence of READMEs for components specified as +// default components in the collector. Looking for default components being enabled +// in the collector gives a reasonable measure of the components that need to be +// documented. Note, that for this test to work, the underlying assumption is +// the imports in "service/defaultcomponents/defaults.go" are indicative +// of components that require documentation. +func TestComponentDocs(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err, "failed to get working directory: %v") + + // Absolute path to the project root directory + projectPath := filepath.Join(wd, "../../") + + err = componenttest.CheckDocs( + projectPath, + relativeDefaultComponentsPath, + projectGoModule, + ) + require.NoError(t, err) +} diff --git a/internal/otel_collector/service/internal/gen.go b/internal/otel_collector/service/internal/gen.go new file mode 100644 index 00000000000..cfba1fb0031 --- /dev/null +++ b/internal/otel_collector/service/internal/gen.go @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +//go:generate esc -pkg internal -o resources.go -modtime "0" templates/ +//go:generate addlicense -y "" -c "The OpenTelemetry Authors" resources.go diff --git a/internal/otel_collector/service/internal/resources.go b/internal/otel_collector/service/internal/resources.go new file mode 100644 index 00000000000..1efaed7a73c --- /dev/null +++ b/internal/otel_collector/service/internal/resources.go @@ -0,0 +1,323 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by "esc -pkg internal -o resources.go -modtime 0 templates/"; DO NOT EDIT. + +package internal + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "sync" + "time" +) + +type _escLocalFS struct{} + +var _escLocal _escLocalFS + +type _escStaticFS struct{} + +var _escStatic _escStaticFS + +type _escDirectory struct { + fs http.FileSystem + name string +} + +type _escFile struct { + compressed string + size int64 + modtime int64 + local string + isDir bool + + once sync.Once + data []byte + name string +} + +func (_escLocalFS) Open(name string) (http.File, error) { + f, present := _escData[path.Clean(name)] + if !present { + return nil, os.ErrNotExist + } + return os.Open(f.local) +} + +func (_escStaticFS) prepare(name string) (*_escFile, error) { + f, present := _escData[path.Clean(name)] + if !present { + return nil, os.ErrNotExist + } + var err error + f.once.Do(func() { + f.name = path.Base(name) + if f.size == 0 { + return + } + var gr *gzip.Reader + b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed)) + gr, err = gzip.NewReader(b64) + if err != nil { + return + } + f.data, err = ioutil.ReadAll(gr) + }) + if err != nil { + return nil, err + } + return f, nil +} + +func (fs _escStaticFS) Open(name string) (http.File, error) { + f, err := fs.prepare(name) + if err != nil { + return nil, err + } + return f.File() +} + +func (dir _escDirectory) Open(name string) (http.File, error) { + return dir.fs.Open(dir.name + name) +} + +func (f *_escFile) File() (http.File, error) { + type httpFile struct { + *bytes.Reader + *_escFile + } + return &httpFile{ + Reader: bytes.NewReader(f.data), + _escFile: f, + }, nil +} + +func (f *_escFile) Close() error { + return nil +} + +func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { + if !f.isDir { + return nil, fmt.Errorf(" escFile.Readdir: '%s' is not directory", f.name) + } + + fis, ok := _escDirs[f.local] + if !ok { + return nil, fmt.Errorf(" escFile.Readdir: '%s' is directory, but we have no info about content of this dir, local=%s", f.name, f.local) + } + limit := count + if count <= 0 || limit > len(fis) { + limit = len(fis) + } + + if len(fis) == 0 && count > 0 { + return nil, io.EOF + } + + return fis[0:limit], nil +} + +func (f *_escFile) Stat() (os.FileInfo, error) { + return f, nil +} + +func (f *_escFile) Name() string { + return f.name +} + +func (f *_escFile) Size() int64 { + return f.size +} + +func (f *_escFile) Mode() os.FileMode { + return 0 +} + +func (f *_escFile) ModTime() time.Time { + return time.Unix(f.modtime, 0) +} + +func (f *_escFile) IsDir() bool { + return f.isDir +} + +func (f *_escFile) Sys() interface{} { + return f +} + +// FS returns a http.Filesystem for the embedded assets. If useLocal is true, +// the filesystem's contents are instead used. +func FS(useLocal bool) http.FileSystem { + if useLocal { + return _escLocal + } + return _escStatic +} + +// Dir returns a http.Filesystem for the embedded assets on a given prefix dir. +// If useLocal is true, the filesystem's contents are instead used. +func Dir(useLocal bool, name string) http.FileSystem { + if useLocal { + return _escDirectory{fs: _escLocal, name: name} + } + return _escDirectory{fs: _escStatic, name: name} +} + +// FSByte returns the named file from the embedded assets. If useLocal is +// true, the filesystem's contents are instead used. +func FSByte(useLocal bool, name string) ([]byte, error) { + if useLocal { + f, err := _escLocal.Open(name) + if err != nil { + return nil, err + } + b, err := ioutil.ReadAll(f) + _ = f.Close() + return b, err + } + f, err := _escStatic.prepare(name) + if err != nil { + return nil, err + } + return f.data, nil +} + +// FSMustByte is the same as FSByte, but panics if name is not present. +func FSMustByte(useLocal bool, name string) []byte { + b, err := FSByte(useLocal, name) + if err != nil { + panic(err) + } + return b +} + +// FSString is the string version of FSByte. +func FSString(useLocal bool, name string) (string, error) { + b, err := FSByte(useLocal, name) + return string(b), err +} + +// FSMustString is the string version of FSMustByte. +func FSMustString(useLocal bool, name string) string { + return string(FSMustByte(useLocal, name)) +} + +var _escData = map[string]*_escFile{ + + "/templates/component_header.html": { + name: "component_header.html", + local: "templates/component_header.html", + size: 156, + modtime: 0, + compressed: ` +H4sIAAAAAAAC/1SMsQqDMBRFd7/iIq7q5lBiltKt9B8CPklQX6R1e9x/L6ZQ2vXcc65ZE3AZ0V3ztmcV +PW467TnpQVZmzZp0Kfs96VJQizTjw1uyAgAXB+8C4lPmsT4fydqbdY+wCen64F0fB19iWV/yF/54X0en +U3kHAAD//zT+SdCcAAAA +`, + }, + + "/templates/extensions_table.html": { + name: "extensions_table.html", + local: "templates/extensions_table.html", + size: 353, + modtime: 0, + compressed: ` +H4sIAAAAAAAC/2SQwU7DMBBE7/2KlemRNJwjxxwQHDnwB248DRbOOnK2tGD531HTQIvqk1fzZjU7Wuw2 +gCb5CmjVNiaHVE2j7Tz3DT0osyIiynltqWlp8xSHMTJYntmN0bOUsgDJcg9ap3jw7HC8n7+z5y0epgU7 +oxX5HeETfMGv9NPTkv4i2e6jT3HPrqE7AEui8yaECbdWkzPYUXWlaHFkg++5VR1YkJTRlt4Tdq06HVfK +4zeOAp58ZLYD2pw3L/sQXu2AUpT5N+raGl2Lu0TRtaTfqsCulJWu52bNTwAAAP//sz5qjmEBAAA= +`, + }, + + "/templates/footer.html": { + name: "footer.html", + local: "templates/footer.html", + size: 15, + modtime: 0, + compressed: ` +H4sIAAAAAAAC/7LRT8pPqbTjstHPKMnNsQMEAAD//wEFevAPAAAA +`, + }, + + "/templates/header.html": { + name: "header.html", + local: "templates/header.html", + size: 467, + modtime: 0, + compressed: ` +H4sIAAAAAAAC/5TRMU8sIRAH8P4+BY/25eC9szGGxUItLIwW11giO7uMB8wG5rxsLvfdDdnTxNhoBeFP +fpnM3/y5fbzZPj/dicAp2pVph4guj52ELK0J4Hq7EkIIk4Cd8MGVCtzJPQ/rS3mOGDmCPR7Vtl1OJ6OX +lyWNmHeiQOxkDVTY71mgpyxFKDB0UuvD4aBogswQIQGXWSHpwb21Xwo9Sf1d4jlCDQD8wQTmqV5pPVDm +qkaiMYKbsCpPSTfpenAJ49w9OIaCLv6995Sr/AXtqQc1Aqc+tgn/qwv1T6czpzD3ONJ6wrxTCbPy9ROv +vuDEoocBiqjF/5RszGuV1uhFsCujl0bMC/Vz62vzZe1hY98DAAD//7qRGmLTAQAA +`, + }, + + "/templates/pipelines_table.html": { + name: "pipelines_table.html", + local: "templates/pipelines_table.html", + size: 1946, + modtime: 0, + compressed: ` +H4sIAAAAAAAC/7SVwXLTMBCG7zyFxnRyIjVcU1scSpnhAMN0eAFZ2gRNlZVmJbdujd+dsWyrTp0LtL5k +rOjX/tlv/8hFEJUB5sOjgTKrLCmgrXdCajzs2MeMv2OMsSLQ8DAsFJPWeCew/MSE0QcsDewDLyr+tTbm +hzhCkVe8yIM6OcU3WHl3NXz+mS8W0oWBBAxAvcU3dHX49ejW9PheBxHAX1v09RHUFxHEim63IEHfA/kV +PX6SleC9XdXkpnGWwqKRIp/i07YXgu1Kdnltj84iYLhB5azG0HWjgAQegF2QfdCooPkQH+OZW/vgR9kg +3TK9Z3AP+Cyf7Y+5TdEW8u5Atka1Y+8BIOOzSmA8LI/ytgVUbDvb6VG1bW93OUW962Kn/wZxKpLC/Koq +Z+L6X/XGgWbDRGeETkcDMo0GZD+a+CNSil+AjLUF+02wL7M+AF33+clpB0YjoDhCuQC6eZJTPpIA5Mn3 +dxpVSaNlxidFkQu+dK/oZSuAaj7VN0a1IUF0dZ6eIzvRc2QTvef/5yr4HNklPjd5Rn5Rcpbf2XbWJZhw +QeMmXNC4hCvdNKvQgsYtacFoGWFFxSvCNl+lu3HQFXl8JfO/AQAA//9We3KLmgcAAA== +`, + }, + + "/templates/properties_table.html": { + name: "properties_table.html", + local: "templates/properties_table.html", + size: 420, + modtime: 0, + compressed: ` +H4sIAAAAAAAC/2SRwW7DIBBE7/6KVRr1VMc5u5gfqFT11Ds2U8sqWVuwqRoR/r1yTCpb4YAEO48ZDarV +MR7ezQkp1apqdaHEtA4U5OLQ7NrRW/gyTKYbuK/puNMFEVGMtB/Y4pfqho6UUr71hnvk0Qvt4XACyyw6 +fPhxgpcBIasXoqThi/ADztRqOC8l/j+L6b57P57Z1vQEIEdZnoELeER1jGBL5WqixJJxQ89NBxZ4favg +nvTaQ95wSWnuQlVi9RrUz9yG6XXZr+vDg3TrsTX4NO6M2WLDVOLv3YJtSoWqbl+h/wIAAP//aLmk3KQB +AAA= +`, + }, + + "/templates": { + name: "templates", + local: `templates/`, + isDir: true, + }, +} + +var _escDirs = map[string][]os.FileInfo{ + + "templates/": { + _escData["/templates/component_header.html"], + _escData["/templates/extensions_table.html"], + _escData["/templates/footer.html"], + _escData["/templates/header.html"], + _escData["/templates/pipelines_table.html"], + _escData["/templates/properties_table.html"], + }, +} diff --git a/internal/otel_collector/service/internal/telemetry/process_telemetry.go b/internal/otel_collector/service/internal/telemetry/process_telemetry.go new file mode 100644 index 00000000000..43c6ff1942a --- /dev/null +++ b/internal/otel_collector/service/internal/telemetry/process_telemetry.go @@ -0,0 +1,183 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telemetry + +import ( + "context" + "os" + "runtime" + "time" + + "github.com/shirou/gopsutil/process" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +// ProcessMetricsViews is a struct that contains views related to process metrics (cpu, mem, etc) +type ProcessMetricsViews struct { + prevTimeUnixNano int64 + ballastSizeBytes uint64 + views []*view.View + done chan struct{} + proc *process.Process +} + +var mUptime = stats.Float64( + "process/uptime", + "Uptime of the process", + stats.UnitSeconds) +var viewProcessUptime = &view.View{ + Name: mUptime.Name(), + Description: mUptime.Description(), + Measure: mUptime, + Aggregation: view.Sum(), + TagKeys: nil, +} + +var mRuntimeAllocMem = stats.Int64( + "process/runtime/heap_alloc_bytes", + "Bytes of allocated heap objects (see 'go doc runtime.MemStats.HeapAlloc')", + stats.UnitBytes) +var viewAllocMem = &view.View{ + Name: mRuntimeAllocMem.Name(), + Description: mRuntimeAllocMem.Description(), + Measure: mRuntimeAllocMem, + Aggregation: view.LastValue(), + TagKeys: nil, +} + +var mRuntimeTotalAllocMem = stats.Int64( + "process/runtime/total_alloc_bytes", + "Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')", + stats.UnitBytes) +var viewTotalAllocMem = &view.View{ + Name: mRuntimeTotalAllocMem.Name(), + Description: mRuntimeTotalAllocMem.Description(), + Measure: mRuntimeTotalAllocMem, + Aggregation: view.LastValue(), + TagKeys: nil, +} + +var mRuntimeSysMem = stats.Int64( + "process/runtime/total_sys_memory_bytes", + "Total bytes of memory obtained from the OS (see 'go doc runtime.MemStats.Sys')", + stats.UnitBytes) +var viewSysMem = &view.View{ + Name: mRuntimeSysMem.Name(), + Description: mRuntimeSysMem.Description(), + Measure: mRuntimeSysMem, + Aggregation: view.LastValue(), + TagKeys: nil, +} + +var mCPUSeconds = stats.Float64( + "process/cpu_seconds", + "Total CPU user and system time in seconds", + stats.UnitSeconds) +var viewCPUSeconds = &view.View{ + Name: mCPUSeconds.Name(), + Description: mCPUSeconds.Description(), + Measure: mCPUSeconds, + Aggregation: view.LastValue(), + TagKeys: nil, +} + +var mRSSMemory = stats.Int64( + "process/memory/rss", + "Total physical memory (resident set size)", + stats.UnitBytes) +var viewRSSMemory = &view.View{ + Name: mRSSMemory.Name(), + Description: mRSSMemory.Description(), + Measure: mRSSMemory, + Aggregation: view.LastValue(), + TagKeys: nil, +} + +// NewProcessMetricsViews creates a new set of ProcessMetrics (mem, cpu) that can be used to measure +// basic information about this process. +func NewProcessMetricsViews(ballastSizeBytes uint64) (*ProcessMetricsViews, error) { + pmv := &ProcessMetricsViews{ + prevTimeUnixNano: time.Now().UnixNano(), + ballastSizeBytes: ballastSizeBytes, + views: []*view.View{viewProcessUptime, viewAllocMem, viewTotalAllocMem, viewSysMem, viewCPUSeconds, viewRSSMemory}, + done: make(chan struct{}), + } + + pid := os.Getpid() + + var err error + pmv.proc, err = process.NewProcess(int32(pid)) + if err != nil { + return nil, err + } + + return pmv, nil +} + +// StartCollection starts a ticker'd goroutine that will update the PMV measurements every 5 seconds +func (pmv *ProcessMetricsViews) StartCollection() { + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + pmv.updateViews() + case <-pmv.done: + return + } + } + }() +} + +// Views returns the views internal to the PMV. +func (pmv *ProcessMetricsViews) Views() []*view.View { + return pmv.views +} + +// StopCollection stops the collection of the process metric information. +func (pmv *ProcessMetricsViews) StopCollection() { + close(pmv.done) +} + +func (pmv *ProcessMetricsViews) updateViews() { + now := time.Now().UnixNano() + stats.Record(context.Background(), mUptime.M(float64(now-pmv.prevTimeUnixNano)/1e9)) + pmv.prevTimeUnixNano = now + + ms := &runtime.MemStats{} + pmv.readMemStats(ms) + stats.Record(context.Background(), mRuntimeAllocMem.M(int64(ms.Alloc))) + stats.Record(context.Background(), mRuntimeTotalAllocMem.M(int64(ms.TotalAlloc))) + stats.Record(context.Background(), mRuntimeSysMem.M(int64(ms.Sys))) + + if pmv.proc != nil { + if times, err := pmv.proc.Times(); err == nil { + stats.Record(context.Background(), mCPUSeconds.M(times.Total())) + } + if mem, err := pmv.proc.MemoryInfo(); err == nil { + stats.Record(context.Background(), mRSSMemory.M(int64(mem.RSS))) + } + } +} + +func (pmv *ProcessMetricsViews) readMemStats(ms *runtime.MemStats) { + runtime.ReadMemStats(ms) + ms.Alloc -= pmv.ballastSizeBytes + ms.HeapAlloc -= pmv.ballastSizeBytes + ms.HeapSys -= pmv.ballastSizeBytes + ms.HeapInuse -= pmv.ballastSizeBytes +} diff --git a/internal/otel_collector/service/internal/telemetry/process_telemetry_test.go b/internal/otel_collector/service/internal/telemetry/process_telemetry_test.go new file mode 100644 index 00000000000..593e8296119 --- /dev/null +++ b/internal/otel_collector/service/internal/telemetry/process_telemetry_test.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telemetry + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opencensus.io/stats/view" +) + +func TestProcessTelemetry(t *testing.T) { + const ballastSizeBytes uint64 = 0 + + pmv, err := NewProcessMetricsViews(ballastSizeBytes) + require.NoError(t, err) + assert.NotNil(t, pmv) + + expectedViews := []string{ + // Changing a metric name is a breaking change. + // Adding new metrics is ok as long it follows the conventions described at + // https://pkg.go.dev/go.opentelemetry.io/collector/obsreport?tab=doc#hdr-Naming_Convention_for_New_Metrics + "process/uptime", + "process/runtime/heap_alloc_bytes", + "process/runtime/total_alloc_bytes", + "process/runtime/total_sys_memory_bytes", + "process/cpu_seconds", + "process/memory/rss", + } + processViews := pmv.Views() + assert.Len(t, processViews, len(expectedViews)) + + require.NoError(t, view.Register(processViews...)) + defer view.Unregister(processViews...) + + // Check that the views are actually filled. + pmv.updateViews() + <-time.After(200 * time.Millisecond) + + for _, viewName := range expectedViews { + rows, err := view.RetrieveData(viewName) + require.NoError(t, err, viewName) + + require.Len(t, rows, 1, viewName) + row := rows[0] + assert.Len(t, row.Tags, 0) + + var value float64 + if viewName == "process/uptime" { + value = row.Data.(*view.SumData).Value + } else { + value = row.Data.(*view.LastValueData).Value + } + + if viewName == "process/uptime" || viewName == "process/cpu_seconds" { + // This likely will still be zero when running the test. + assert.True(t, value >= 0, viewName) + continue + } + + assert.True(t, value > 0, viewName) + } +} diff --git a/internal/otel_collector/service/internal/templates.go b/internal/otel_collector/service/internal/templates.go new file mode 100644 index 00000000000..0d165b7a76c --- /dev/null +++ b/internal/otel_collector/service/internal/templates.go @@ -0,0 +1,152 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "html/template" + "io" + "io/ioutil" + "log" +) + +var ( + fs = FS(false) + templateFunctions = template.FuncMap{ + "even": even, + "getKey": getKey, + "getValue": getValue, + } + componentHeaderTemplate = parseTemplate("component_header") + extensionsTableTemplate = parseTemplate("extensions_table") + headerTemplate = parseTemplate("header") + footerTemplate = parseTemplate("footer") + pipelinesTableTemplate = parseTemplate("pipelines_table") + propertiesTableTemplate = parseTemplate("properties_table") +) + +func parseTemplate(name string) *template.Template { + f, err := fs.Open("/templates/" + name + ".html") + if err != nil { + log.Panicf("%v: %v", name, err) + } + defer f.Close() + text, err := ioutil.ReadAll(f) + if err != nil { + log.Panicf("%v: %v", name, err) + } + return template.Must(template.New(name).Funcs(templateFunctions).Parse(string(text))) +} + +// HeaderData contains data for the header template. +type HeaderData struct { + Title string +} + +// WriteHTMLFooter writes the header. +func WriteHTMLHeader(w io.Writer, hd HeaderData) { + if err := headerTemplate.Execute(w, hd); err != nil { + log.Printf("zpages: executing template: %v", err) + } +} + +// SummaryExtensionsTableData contains data for extensions summary table template. +type SummaryExtensionsTableData struct { + ComponentEndpoint string + Rows []SummaryExtensionsTableRowData +} + +// SummaryExtensionsTableData contains data for one row in extensions summary table template. +type SummaryExtensionsTableRowData struct { + FullName string + Enabled bool +} + +// WriteHTMLSummaryTable writes the summary table for one component type (receivers, processors, exporters). +// Id does not write the header or footer. +func WriteHTMLExtensionsSummaryTable(w io.Writer, spd SummaryExtensionsTableData) { + if err := extensionsTableTemplate.Execute(w, spd); err != nil { + log.Printf("zpages: executing template: %v", err) + } +} + +// SummaryPipelinesTableData contains data for pipelines summary table template. +type SummaryPipelinesTableData struct { + ComponentEndpoint string + Rows []SummaryPipelinesTableRowData +} + +// SummaryPipelinesTableRowData contains data for one row in pipelines summary table template. +type SummaryPipelinesTableRowData struct { + FullName string + InputType string + MutatesConsumedData bool + Receivers []string + Processors []string + Exporters []string +} + +// WriteHTMLSummaryTable writes the summary table for one component type (receivers, processors, exporters). +// Id does not write the header or footer. +func WriteHTMLPipelinesSummaryTable(w io.Writer, spd SummaryPipelinesTableData) { + if err := pipelinesTableTemplate.Execute(w, spd); err != nil { + log.Printf("zpages: executing template: %v", err) + } +} + +// ComponentHeaderData contains data for component header template. +type ComponentHeaderData struct { + Name string + ComponentEndpoint string + Link bool +} + +// WriteHTMLFooter writes the footer. +func WriteHTMLComponentHeader(w io.Writer, chd ComponentHeaderData) { + if err := componentHeaderTemplate.Execute(w, chd); err != nil { + log.Printf("zpages: executing template: %v", err) + } +} + +// PropertiesTableData contains data for properties table template. +type PropertiesTableData struct { + Name string + Properties [][2]string +} + +// WriteHTMLFooter writes the footer. +func WriteHTMLPropertiesTable(w io.Writer, chd PropertiesTableData) { + if err := propertiesTableTemplate.Execute(w, chd); err != nil { + log.Printf("zpages: executing template: %v", err) + } +} + +// WriteHTMLFooter writes the footer. +func WriteHTMLFooter(w io.Writer) { + if err := footerTemplate.Execute(w, nil); err != nil { + log.Printf("zpages: executing template: %v", err) + } +} + +func even(x int) bool { + return x%2 == 0 +} + +func getKey(row [2]string) string { + return row[0] +} + +func getValue(row [2]string) string { + return row[1] +} diff --git a/internal/otel_collector/service/internal/templates/component_header.html b/internal/otel_collector/service/internal/templates/component_header.html new file mode 100644 index 00000000000..463a15cc7e6 --- /dev/null +++ b/internal/otel_collector/service/internal/templates/component_header.html @@ -0,0 +1,7 @@ +{{$a := .ComponentEndpoint}} +{{$link := .Link}} +{{- if $link -}} +

{{.Name}}
+{{- else -}} +
{{.Name}}
+{{- end -}} \ No newline at end of file diff --git a/internal/otel_collector/service/internal/templates/extensions_table.html b/internal/otel_collector/service/internal/templates/extensions_table.html new file mode 100644 index 00000000000..0b39ee933e1 --- /dev/null +++ b/internal/otel_collector/service/internal/templates/extensions_table.html @@ -0,0 +1,11 @@ + + {{$a := .ComponentEndpoint}} + {{range $rowindex, $row := .Rows}} + {{- if even $rowindex}} + + {{else}} + {{end -}} + + + {{end}} +
{{.FullName}}
\ No newline at end of file diff --git a/internal/otel_collector/service/internal/templates/footer.html b/internal/otel_collector/service/internal/templates/footer.html new file mode 100644 index 00000000000..691287b6e35 --- /dev/null +++ b/internal/otel_collector/service/internal/templates/footer.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/internal/otel_collector/service/internal/templates/header.html b/internal/otel_collector/service/internal/templates/header.html new file mode 100644 index 00000000000..c381c0ff6c8 --- /dev/null +++ b/internal/otel_collector/service/internal/templates/header.html @@ -0,0 +1,11 @@ + + + + {{.Title}} + + + + + + +

{{.Title}}

\ No newline at end of file diff --git a/internal/otel_collector/service/internal/templates/pipelines_table.html b/internal/otel_collector/service/internal/templates/pipelines_table.html new file mode 100644 index 00000000000..8798f15862d --- /dev/null +++ b/internal/otel_collector/service/internal/templates/pipelines_table.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + {{$a := .ComponentEndpoint}} + {{range $rowindex, $row := .Rows}} + {{- if even $rowindex}} + + {{else}} + {{end -}} + + + + + + + + {{end}} +
FullName  |  InputType  |  MutatesConsumedData  |  Receivers  |  Processors  |  Exporters
{{$row.FullName}}  |  {{$row.InputType}}  |  {{$row.MutatesConsumedData}}  |   + {{range $recindex, $rec := $row.Receivers}} + {{$rec}} +
+ {{end}} +
  |   + → + {{range $proindex, $pro := $row.Processors}} + {{$pro}} + → + {{end}} +   |   + {{range $expindex, $exp := $row.Exporters}} + {{$exp}} +
+ {{end}} +
\ No newline at end of file diff --git a/internal/otel_collector/service/internal/templates/properties_table.html b/internal/otel_collector/service/internal/templates/properties_table.html new file mode 100644 index 00000000000..a43841ba770 --- /dev/null +++ b/internal/otel_collector/service/internal/templates/properties_table.html @@ -0,0 +1,14 @@ +{{.Name}}: + + {{ $index := 0 }} + {{range $index, $element := .Properties}} + {{- if even $index}} + + {{else}} + {{end -}} + + + + + {{end}} +
{{$element|getKey}}  |  {{$element|getValue}}
\ No newline at end of file diff --git a/internal/otel_collector/service/internal/templates_test.go b/internal/otel_collector/service/internal/templates_test.go new file mode 100644 index 00000000000..4e320a9e3d0 --- /dev/null +++ b/internal/otel_collector/service/internal/templates_test.go @@ -0,0 +1,87 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "bytes" + "html/template" + "testing" + + "github.com/stretchr/testify/assert" +) + +const tmplBody = ` +

{{.Index|even}}

+

{{.Element|getKey}}

+

{{.Element|getValue}}

+` + +const want = ` +

true

+

key

+

value

+` + +type testFuncsInput struct { + Index int + Element [2]string +} + +var tmpl = template.Must(template.New("countTest").Funcs(templateFunctions).Parse(tmplBody)) + +func TestTemplateFuncs(t *testing.T) { + buf := new(bytes.Buffer) + input := testFuncsInput{ + Index: 32, + Element: [2]string{"key", "value"}, + } + assert.NoError(t, tmpl.Execute(buf, input)) + assert.EqualValues(t, want, buf.String()) +} + +func TestNoCrash(t *testing.T) { + buf := new(bytes.Buffer) + assert.NotPanics(t, func() { WriteHTMLHeader(buf, HeaderData{Title: "Foo"}) }) + assert.NotPanics(t, func() { WriteHTMLComponentHeader(buf, ComponentHeaderData{Name: "Bar"}) }) + assert.NotPanics(t, func() { + WriteHTMLComponentHeader(buf, ComponentHeaderData{Name: "Bar", ComponentEndpoint: "pagez", Link: true}) + }) + assert.NotPanics(t, func() { + WriteHTMLPipelinesSummaryTable(buf, SummaryPipelinesTableData{ + ComponentEndpoint: "pagez", + Rows: []SummaryPipelinesTableRowData{{ + FullName: "test", + InputType: "metrics", + MutatesConsumedData: false, + Receivers: []string{"oc"}, + Processors: []string{"nop"}, + Exporters: []string{"oc"}, + }}, + }) + }) + assert.NotPanics(t, func() { + WriteHTMLExtensionsSummaryTable(buf, SummaryExtensionsTableData{ + ComponentEndpoint: "pagez", + Rows: []SummaryExtensionsTableRowData{{ + FullName: "test", + }}, + }) + }) + assert.NotPanics(t, func() { + WriteHTMLPropertiesTable(buf, PropertiesTableData{Name: "Bar", Properties: [][2]string{{"key", "value"}}}) + }) + assert.NotPanics(t, func() { WriteHTMLFooter(buf) }) + assert.NotPanics(t, func() { WriteHTMLFooter(buf) }) +} diff --git a/internal/otel_collector/service/logger.go b/internal/otel_collector/service/logger.go new file mode 100644 index 00000000000..f494972bd05 --- /dev/null +++ b/internal/otel_collector/service/logger.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "flag" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/internal/version" +) + +const ( + logLevelCfg = "log-level" + logProfileCfg = "log-profile" + logFormatCfg = "log-format" +) + +var ( + // Command line pointer to logger level flag configuration. + loggerLevelPtr *string + loggerProfilePtr *string + loggerFormatPtr *string +) + +func loggerFlags(flags *flag.FlagSet) { + loggerLevelPtr = flags.String(logLevelCfg, "INFO", "Output level of logs (DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL)") + loggerProfilePtr = flags.String(logProfileCfg, "", "Logging profile to use (dev, prod)") + + // Note: we use "console" by default for more human-friendly mode of logging (tab delimited, formatted timestamps). + loggerFormatPtr = flags.String(logFormatCfg, "console", "Format of logs to use (json, console)") +} + +func newLogger(options []zap.Option) (*zap.Logger, error) { + var level zapcore.Level + err := (&level).UnmarshalText([]byte(*loggerLevelPtr)) + if err != nil { + return nil, err + } + + conf := zap.NewProductionConfig() + + // Use logger profile if set on command line before falling back + // to default based on build type. + switch *loggerProfilePtr { + case "dev": + conf = zap.NewDevelopmentConfig() + case "prod": + conf = zap.NewProductionConfig() + default: + if version.IsDevBuild() { + conf = zap.NewDevelopmentConfig() + } + } + + conf.Encoding = *loggerFormatPtr + if conf.Encoding == "console" { + // Human-readable timestamps for console format of logs. + conf.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + } + + conf.Level.SetLevel(level) + return conf.Build(options...) +} diff --git a/internal/otel_collector/service/service.go b/internal/otel_collector/service/service.go new file mode 100644 index 00000000000..d0ff423a199 --- /dev/null +++ b/internal/otel_collector/service/service.go @@ -0,0 +1,596 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package service handles the command-line, configuration, and runs the +// OpenTelemetry Collector. +package service + +import ( + "context" + "errors" + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "path" + "runtime" + "sort" + "syscall" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/internal/collector/telemetry" + "go.opentelemetry.io/collector/internal/version" + "go.opentelemetry.io/collector/service/builder" + "go.opentelemetry.io/collector/service/internal" +) + +const ( + servicezPath = "servicez" + pipelinezPath = "pipelinez" + extensionzPath = "extensionz" +) + +// State defines Application's state. +type State int + +const ( + Starting State = iota + Running + Closing + Closed +) + +// GetStateChannel returns state channel of the application. +func (app *Application) GetStateChannel() chan State { + return app.stateChannel +} + +// Application represents a collector application +type Application struct { + info component.ApplicationStartInfo + rootCmd *cobra.Command + v *viper.Viper + logger *zap.Logger + builtExporters builder.Exporters + builtReceivers builder.Receivers + builtPipelines builder.BuiltPipelines + builtExtensions builder.Extensions + stateChannel chan State + + factories component.Factories + config *configmodels.Config + + // stopTestChan is used to terminate the application in end to end tests. + stopTestChan chan struct{} + + // signalsChannel is used to receive termination signals from the OS. + signalsChannel chan os.Signal + + // asyncErrorChannel is used to signal a fatal error from any component. + asyncErrorChannel chan error +} + +// Command returns Application's root command. +func (app *Application) Command() *cobra.Command { + return app.rootCmd +} + +// Parameters holds configuration for creating a new Application. +type Parameters struct { + // Factories component factories. + Factories component.Factories + // ApplicationStartInfo provides application start information. + ApplicationStartInfo component.ApplicationStartInfo + // ConfigFactory that creates the configuration. + // If it is not provided the default factory (FileLoaderConfigFactory) is used. + // The default factory loads the configuration file and overrides component's configuration + // properties supplied via --set command line flag. + ConfigFactory ConfigFactory + // LoggingOptions provides a way to change behavior of zap logging. + LoggingOptions []zap.Option +} + +// ConfigFactory creates config. +// The ConfigFactory implementation should call AddSetFlagProperties to enable configuration passed via `--set` flag. +// Viper and command instances are passed from the Application. +// The factories also belong to the Application and are equal to the factories passed via Parameters. +type ConfigFactory func(v *viper.Viper, cmd *cobra.Command, factories component.Factories) (*configmodels.Config, error) + +// FileLoaderConfigFactory implements ConfigFactory and it creates configuration from file +// and from --set command line flag (if the flag is present). +func FileLoaderConfigFactory(v *viper.Viper, cmd *cobra.Command, factories component.Factories) (*configmodels.Config, error) { + file := builder.GetConfigFile() + if file == "" { + return nil, errors.New("config file not specified") + } + // first load the config file + v.SetConfigFile(file) + err := v.ReadInConfig() + if err != nil { + return nil, fmt.Errorf("error loading config file %q: %v", file, err) + } + + // next overlay the config file with --set flags + if err := AddSetFlagProperties(v, cmd); err != nil { + return nil, fmt.Errorf("failed to process set flag: %v", err) + } + return config.Load(v, factories) +} + +// New creates and returns a new instance of Application. +func New(params Parameters) (*Application, error) { + app := &Application{ + info: params.ApplicationStartInfo, + v: config.NewViper(), + factories: params.Factories, + stateChannel: make(chan State, Closed+1), + } + + factory := params.ConfigFactory + if factory == nil { + // use default factory that loads the configuration file + factory = FileLoaderConfigFactory + } + + rootCmd := &cobra.Command{ + Use: params.ApplicationStartInfo.ExeName, + Long: params.ApplicationStartInfo.LongName, + RunE: func(cmd *cobra.Command, args []string) error { + err := app.init(params.LoggingOptions) + if err != nil { + return err + } + + err = app.execute(context.Background(), factory) + if err != nil { + return err + } + + return nil + }, + } + + // TODO: coalesce this code and expose this information to other components. + flagSet := new(flag.FlagSet) + addFlagsFns := []func(*flag.FlagSet){ + configtelemetry.Flags, + telemetry.Flags, + builder.Flags, + loggerFlags, + } + for _, addFlags := range addFlagsFns { + addFlags(flagSet) + } + rootCmd.Flags().AddGoFlagSet(flagSet) + addSetFlag(rootCmd.Flags()) + + app.rootCmd = rootCmd + + return app, nil +} + +// ReportFatalError is used to report to the host that the receiver encountered +// a fatal error (i.e.: an error that the instance can't recover from) after +// its start function has already returned. +func (app *Application) ReportFatalError(err error) { + app.asyncErrorChannel <- err +} + +// GetLogger returns logger used by the Application. +// The logger is initialized after application start. +func (app *Application) GetLogger() *zap.Logger { + return app.logger +} + +func (app *Application) GetFactory(kind component.Kind, componentType configmodels.Type) component.Factory { + switch kind { + case component.KindReceiver: + return app.factories.Receivers[componentType] + case component.KindProcessor: + return app.factories.Processors[componentType] + case component.KindExporter: + return app.factories.Exporters[componentType] + case component.KindExtension: + return app.factories.Extensions[componentType] + } + return nil +} + +func (app *Application) GetExtensions() map[configmodels.Extension]component.ServiceExtension { + return app.builtExtensions.ToMap() +} + +func (app *Application) GetExporters() map[configmodels.DataType]map[configmodels.Exporter]component.Exporter { + return app.builtExporters.ToMapByDataType() +} + +func (app *Application) RegisterZPages(mux *http.ServeMux, pathPrefix string) { + mux.HandleFunc(path.Join(pathPrefix, servicezPath), app.handleServicezRequest) + mux.HandleFunc(path.Join(pathPrefix, pipelinezPath), app.handlePipelinezRequest) + mux.HandleFunc(path.Join(pathPrefix, extensionzPath), app.handleExtensionzRequest) +} + +func (app *Application) Shutdown() { + // TODO: Implement a proper shutdown with graceful draining of the pipeline. + // See https://github.com/open-telemetry/opentelemetry-collector/issues/483. + defer func() { + if r := recover(); r != nil { + app.logger.Info("stopTestChan already closed") + } + }() + close(app.stopTestChan) +} + +func (app *Application) init(options []zap.Option) error { + l, err := newLogger(options) + if err != nil { + return fmt.Errorf("failed to get logger: %w", err) + } + app.logger = l + return nil +} + +func (app *Application) setupTelemetry(ballastSizeBytes uint64) error { + app.logger.Info("Setting up own telemetry...") + + err := applicationTelemetry.init(app.asyncErrorChannel, ballastSizeBytes, app.logger) + if err != nil { + return fmt.Errorf("failed to initialize telemetry: %w", err) + } + + return nil +} + +// runAndWaitForShutdownEvent waits for one of the shutdown events that can happen. +func (app *Application) runAndWaitForShutdownEvent() { + app.logger.Info("Everything is ready. Begin running and processing data.") + + // plug SIGTERM signal into a channel. + app.signalsChannel = make(chan os.Signal, 1) + signal.Notify(app.signalsChannel, os.Interrupt, syscall.SIGTERM) + + // set the channel to stop testing. + app.stopTestChan = make(chan struct{}) + app.stateChannel <- Running + select { + case err := <-app.asyncErrorChannel: + app.logger.Error("Asynchronous error received, terminating process", zap.Error(err)) + case s := <-app.signalsChannel: + app.logger.Info("Received signal from OS", zap.String("signal", s.String())) + case <-app.stopTestChan: + app.logger.Info("Received stop test request") + } + app.stateChannel <- Closing +} + +func (app *Application) setupConfigurationComponents(ctx context.Context, factory ConfigFactory) error { + if err := configcheck.ValidateConfigFromFactories(app.factories); err != nil { + return err + } + + app.logger.Info("Loading configuration...") + cfg, err := factory(app.v, app.rootCmd, app.factories) + if err != nil { + return fmt.Errorf("cannot load configuration: %w", err) + } + err = config.ValidateConfig(cfg, app.logger) + if err != nil { + return fmt.Errorf("cannot load configuration: %w", err) + } + + app.config = cfg + app.logger.Info("Applying configuration...") + + err = app.setupExtensions(ctx) + if err != nil { + return fmt.Errorf("cannot setup extensions: %w", err) + } + + err = app.setupPipelines(ctx) + if err != nil { + return fmt.Errorf("cannot setup pipelines: %w", err) + } + + return nil +} + +func (app *Application) setupExtensions(ctx context.Context) error { + var err error + app.builtExtensions, err = builder.NewExtensionsBuilder(app.logger, app.info, app.config, app.factories.Extensions).Build() + if err != nil { + return fmt.Errorf("cannot build builtExtensions: %w", err) + } + app.logger.Info("Starting extensions...") + return app.builtExtensions.StartAll(ctx, app) +} + +func (app *Application) setupPipelines(ctx context.Context) error { + // Pipeline is built backwards, starting from exporters, so that we create objects + // which are referenced before objects which reference them. + + // First create exporters. + var err error + app.builtExporters, err = builder.NewExportersBuilder(app.logger, app.info, app.config, app.factories.Exporters).Build() + if err != nil { + return fmt.Errorf("cannot build builtExporters: %w", err) + } + + app.logger.Info("Starting exporters...") + err = app.builtExporters.StartAll(ctx, app) + if err != nil { + return fmt.Errorf("cannot start builtExporters: %w", err) + } + + // Create pipelines and their processors and plug exporters to the + // end of the pipelines. + app.builtPipelines, err = builder.NewPipelinesBuilder(app.logger, app.info, app.config, app.builtExporters, app.factories.Processors).Build() + if err != nil { + return fmt.Errorf("cannot build pipelines: %w", err) + } + + app.logger.Info("Starting processors...") + err = app.builtPipelines.StartProcessors(ctx, app) + if err != nil { + return fmt.Errorf("cannot start processors: %w", err) + } + + // Create receivers and plug them into the start of the pipelines. + app.builtReceivers, err = builder.NewReceiversBuilder(app.logger, app.info, app.config, app.builtPipelines, app.factories.Receivers).Build() + if err != nil { + return fmt.Errorf("cannot build receivers: %w", err) + } + + app.logger.Info("Starting receivers...") + err = app.builtReceivers.StartAll(ctx, app) + if err != nil { + return fmt.Errorf("cannot start receivers: %w", err) + } + + return nil +} + +func (app *Application) shutdownPipelines(ctx context.Context) error { + // Shutdown order is the reverse of building: first receivers, then flushing pipelines + // giving senders a chance to send all their data. This may take time, the allowed + // time should be part of configuration. + + var errs []error + + app.logger.Info("Stopping receivers...") + err := app.builtReceivers.ShutdownAll(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("failed to stop receivers: %w", err)) + } + + app.logger.Info("Stopping processors...") + err = app.builtPipelines.ShutdownProcessors(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("failed to shutdown processors: %w", err)) + } + + app.logger.Info("Stopping exporters...") + err = app.builtExporters.ShutdownAll(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("failed to shutdown exporters: %w", err)) + } + + return componenterror.CombineErrors(errs) +} + +func (app *Application) shutdownExtensions(ctx context.Context) error { + app.logger.Info("Stopping extensions...") + err := app.builtExtensions.ShutdownAll(ctx) + if err != nil { + return fmt.Errorf("failed to shutdown extensions: %w", err) + } + return nil +} + +func (app *Application) execute(ctx context.Context, factory ConfigFactory) error { + app.logger.Info("Starting "+app.info.LongName+"...", + zap.String("Version", app.info.Version), + zap.String("GitHash", app.info.GitHash), + zap.Int("NumCPU", runtime.NumCPU()), + ) + app.stateChannel <- Starting + + // Set memory ballast + ballast, ballastSizeBytes := app.createMemoryBallast() + + app.asyncErrorChannel = make(chan error) + + // Setup everything. + err := app.setupTelemetry(ballastSizeBytes) + if err != nil { + return err + } + + err = app.setupConfigurationComponents(ctx, factory) + if err != nil { + return err + } + + err = app.builtExtensions.NotifyPipelineReady() + if err != nil { + return err + } + + // Everything is ready, now run until an event requiring shutdown happens. + app.runAndWaitForShutdownEvent() + + // Accumulate errors and proceed with shutting down remaining components. + var errs []error + + // Begin shutdown sequence. + runtime.KeepAlive(ballast) + app.logger.Info("Starting shutdown...") + + err = app.builtExtensions.NotifyPipelineNotReady() + if err != nil { + errs = append(errs, fmt.Errorf("failed to notify that pipeline is not ready: %w", err)) + } + + err = app.shutdownPipelines(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("failed to shutdown pipelines: %w", err)) + } + + err = app.shutdownExtensions(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("failed to shutdown extensions: %w", err)) + } + + err = applicationTelemetry.shutdown() + if err != nil { + errs = append(errs, fmt.Errorf("failed to shutdown extensions: %w", err)) + } + + app.logger.Info("Shutdown complete.") + app.stateChannel <- Closed + close(app.stateChannel) + + return componenterror.CombineErrors(errs) +} + +// Run starts the collector according to the command and configuration +// given by the user, and waits for it to complete. +func (app *Application) Run() error { + // From this point on do not show usage in case of error. + app.rootCmd.SilenceUsage = true + + return app.rootCmd.Execute() +} + +const ( + zPipelineName = "zpipelinename" + zComponentName = "zcomponentname" + zComponentKind = "zcomponentkind" + zExtensionName = "zextensionname" +) + +func (app *Application) handleServicezRequest(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + w.Header().Set("Content-Type", "text/html; charset=utf-8") + internal.WriteHTMLHeader(w, internal.HeaderData{Title: "Service"}) + internal.WriteHTMLComponentHeader(w, internal.ComponentHeaderData{ + Name: "Pipelines", + ComponentEndpoint: pipelinezPath, + Link: true, + }) + internal.WriteHTMLComponentHeader(w, internal.ComponentHeaderData{ + Name: "Extensions", + ComponentEndpoint: extensionzPath, + Link: true, + }) + internal.WriteHTMLPropertiesTable(w, internal.PropertiesTableData{Name: "Build And Runtime", Properties: version.InfoVar}) + internal.WriteHTMLFooter(w) +} + +func (app *Application) handlePipelinezRequest(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + w.Header().Set("Content-Type", "text/html; charset=utf-8") + pipelineName := r.Form.Get(zPipelineName) + componentName := r.Form.Get(zComponentName) + componentKind := r.Form.Get(zComponentKind) + internal.WriteHTMLHeader(w, internal.HeaderData{Title: "Pipelines"}) + internal.WriteHTMLPipelinesSummaryTable(w, app.getPipelinesSummaryTableData()) + if pipelineName != "" && componentName != "" && componentKind != "" { + fullName := componentName + if componentKind == "processor" { + fullName = pipelineName + "/" + componentName + } + internal.WriteHTMLComponentHeader(w, internal.ComponentHeaderData{ + Name: componentKind + ": " + fullName, + }) + // TODO: Add config + status info. + } + internal.WriteHTMLFooter(w) +} + +func (app *Application) handleExtensionzRequest(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + w.Header().Set("Content-Type", "text/html; charset=utf-8") + extensionName := r.Form.Get(zExtensionName) + internal.WriteHTMLHeader(w, internal.HeaderData{Title: "Extensions"}) + internal.WriteHTMLExtensionsSummaryTable(w, app.getExtensionsSummaryTableData()) + if extensionName != "" { + internal.WriteHTMLComponentHeader(w, internal.ComponentHeaderData{ + Name: extensionName, + }) + // TODO: Add config + status info. + } + internal.WriteHTMLFooter(w) +} + +func (app *Application) getPipelinesSummaryTableData() internal.SummaryPipelinesTableData { + data := internal.SummaryPipelinesTableData{ + ComponentEndpoint: pipelinezPath, + } + + data.Rows = make([]internal.SummaryPipelinesTableRowData, 0, len(app.builtExtensions)) + for c, p := range app.builtPipelines { + row := internal.SummaryPipelinesTableRowData{ + FullName: c.Name, + InputType: string(c.InputType), + MutatesConsumedData: p.MutatesConsumedData, + Receivers: c.Receivers, + Processors: c.Processors, + Exporters: c.Exporters, + } + data.Rows = append(data.Rows, row) + } + + sort.Slice(data.Rows, func(i, j int) bool { + return data.Rows[i].FullName < data.Rows[j].FullName + }) + return data +} + +func (app *Application) getExtensionsSummaryTableData() internal.SummaryExtensionsTableData { + data := internal.SummaryExtensionsTableData{ + ComponentEndpoint: extensionzPath, + } + + data.Rows = make([]internal.SummaryExtensionsTableRowData, 0, len(app.builtExtensions)) + for c := range app.builtExtensions { + row := internal.SummaryExtensionsTableRowData{FullName: c.Name()} + data.Rows = append(data.Rows, row) + } + + sort.Slice(data.Rows, func(i, j int) bool { + return data.Rows[i].FullName < data.Rows[j].FullName + }) + return data +} + +func (app *Application) createMemoryBallast() ([]byte, uint64) { + ballastSizeMiB := builder.MemBallastSize() + if ballastSizeMiB > 0 { + ballastSizeBytes := uint64(ballastSizeMiB) * 1024 * 1024 + ballast := make([]byte, ballastSizeBytes) + app.logger.Info("Using memory ballast", zap.Int("MiBs", ballastSizeMiB)) + return ballast, ballastSizeBytes + } + return nil, 0 +} diff --git a/internal/otel_collector/service/service_test.go b/internal/otel_collector/service/service_test.go new file mode 100644 index 00000000000..2d1125d005c --- /dev/null +++ b/internal/otel_collector/service/service_test.go @@ -0,0 +1,649 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package collector handles the command-line, configuration, and runs the OC collector. +package service + +import ( + "bufio" + "context" + "errors" + "flag" + "fmt" + "net/http" + "sort" + "strconv" + "strings" + "syscall" + "testing" + "time" + + "github.com/prometheus/common/expfmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/processor/attributesprocessor" + "go.opentelemetry.io/collector/processor/batchprocessor" + "go.opentelemetry.io/collector/receiver/jaegerreceiver" + "go.opentelemetry.io/collector/service/builder" + "go.opentelemetry.io/collector/service/defaultcomponents" + "go.opentelemetry.io/collector/testutil" +) + +func TestApplication_Start(t *testing.T) { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + loggingHookCalled := false + hook := func(entry zapcore.Entry) error { + loggingHookCalled = true + return nil + } + + app, err := New(Parameters{Factories: factories, ApplicationStartInfo: componenttest.TestApplicationStartInfo(), LoggingOptions: []zap.Option{zap.Hooks(hook)}}) + require.NoError(t, err) + assert.Equal(t, app.rootCmd, app.Command()) + + const testPrefix = "a_test" + metricsPort := testutil.GetAvailablePort(t) + app.rootCmd.SetArgs([]string{ + "--config=testdata/otelcol-config.yaml", + "--metrics-addr=localhost:" + strconv.FormatUint(uint64(metricsPort), 10), + "--metrics-prefix=" + testPrefix, + }) + + appDone := make(chan struct{}) + go func() { + defer close(appDone) + assert.NoError(t, app.Run()) + }() + + assert.Equal(t, Starting, <-app.GetStateChannel()) + assert.Equal(t, Running, <-app.GetStateChannel()) + require.True(t, isAppAvailable(t, "http://localhost:13133")) + assert.Equal(t, app.logger, app.GetLogger()) + assert.True(t, loggingHookCalled) + + // All labels added to all collector metrics by default are listed below. + // These labels are hard coded here in order to avoid inadvertent changes: + // at this point changing labels should be treated as a breaking changing + // and requires a good justification. The reason is that changes to metric + // names or labels can break alerting, dashboards, etc that are used to + // monitor the Collector in production deployments. + mandatoryLabels := []string{ + "service_instance_id", + } + assertMetrics(t, testPrefix, metricsPort, mandatoryLabels) + + app.signalsChannel <- syscall.SIGTERM + <-appDone + assert.Equal(t, Closing, <-app.GetStateChannel()) + assert.Equal(t, Closed, <-app.GetStateChannel()) +} + +type mockAppTelemetry struct{} + +func (tel *mockAppTelemetry) init(chan<- error, uint64, *zap.Logger) error { + return nil +} + +func (tel *mockAppTelemetry) shutdown() error { + return errors.New("err1") +} + +func TestApplication_ReportError(t *testing.T) { + // use a mock AppTelemetry struct to return an error on shutdown + preservedAppTelemetry := applicationTelemetry + applicationTelemetry = &mockAppTelemetry{} + defer func() { applicationTelemetry = preservedAppTelemetry }() + + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + app, err := New(Parameters{Factories: factories, ApplicationStartInfo: componenttest.TestApplicationStartInfo()}) + require.NoError(t, err) + + app.rootCmd.SetArgs([]string{"--config=testdata/otelcol-config-minimal.yaml"}) + + appDone := make(chan struct{}) + go func() { + defer close(appDone) + assert.EqualError(t, app.Run(), "failed to shutdown extensions: err1") + }() + + assert.Equal(t, Starting, <-app.GetStateChannel()) + assert.Equal(t, Running, <-app.GetStateChannel()) + app.ReportFatalError(errors.New("err2")) + <-appDone + assert.Equal(t, Closing, <-app.GetStateChannel()) + assert.Equal(t, Closed, <-app.GetStateChannel()) +} + +func TestApplication_StartAsGoRoutine(t *testing.T) { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + params := Parameters{ + ApplicationStartInfo: componenttest.TestApplicationStartInfo(), + ConfigFactory: func(_ *viper.Viper, _ *cobra.Command, factories component.Factories) (*configmodels.Config, error) { + return constructMimumalOpConfig(t, factories), nil + }, + Factories: factories, + } + app, err := New(params) + require.NoError(t, err) + app.Command().SetArgs([]string{ + "--metrics-level=NONE", + }) + + appDone := make(chan struct{}) + go func() { + defer close(appDone) + appErr := app.Run() + if appErr != nil { + err = appErr + } + }() + + assert.Equal(t, Starting, <-app.GetStateChannel()) + assert.Equal(t, Running, <-app.GetStateChannel()) + + app.Shutdown() + app.Shutdown() + <-appDone + assert.Equal(t, Closing, <-app.GetStateChannel()) + assert.Equal(t, Closed, <-app.GetStateChannel()) +} + +// isAppAvailable checks if the healthcheck server at the given endpoint is +// returning `available`. +func isAppAvailable(t *testing.T, healthCheckEndPoint string) bool { + client := &http.Client{} + resp, err := client.Get(healthCheckEndPoint) + require.NoError(t, err) + + defer resp.Body.Close() + return resp.StatusCode == http.StatusOK +} + +func assertMetrics(t *testing.T, prefix string, metricsPort uint16, mandatoryLabels []string) { + client := &http.Client{} + resp, err := client.Get(fmt.Sprintf("http://localhost:%d/metrics", metricsPort)) + require.NoError(t, err) + + defer resp.Body.Close() + reader := bufio.NewReader(resp.Body) + + var parser expfmt.TextParser + parsed, err := parser.TextToMetricFamilies(reader) + require.NoError(t, err) + + for metricName, metricFamily := range parsed { + // require is used here so test fails with a single message. + require.True( + t, + strings.HasPrefix(metricName, prefix), + "expected prefix %q but string starts with %q", + prefix, + metricName[:len(prefix)+1]+"...") + + for _, metric := range metricFamily.Metric { + var labelNames []string + for _, labelPair := range metric.Label { + labelNames = append(labelNames, *labelPair.Name) + } + + for _, mandatoryLabel := range mandatoryLabels { + // require is used here so test fails with a single message. + require.Contains(t, labelNames, mandatoryLabel, "mandatory label %q not present", mandatoryLabel) + } + } + } +} + +func TestApplication_setupExtensions(t *testing.T) { + exampleExtensionFactory := &componenttest.ExampleExtensionFactory{FailCreation: true} + exampleExtensionConfig := &componenttest.ExampleExtensionCfg{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: exampleExtensionFactory.Type(), + NameVal: string(exampleExtensionFactory.Type()), + }, + } + + badExtensionFactory := &badExtensionFactory{} + badExtensionFactoryConfig := &configmodels.ExtensionSettings{ + TypeVal: "bf", + NameVal: "bf", + } + + tests := []struct { + name string + factories component.Factories + config *configmodels.Config + wantErrMsg string + }{ + { + name: "extension_not_configured", + config: &configmodels.Config{ + Service: configmodels.Service{ + Extensions: []string{ + "myextension", + }, + }, + }, + wantErrMsg: "cannot build builtExtensions: extension \"myextension\" is not configured", + }, + { + name: "missing_extension_factory", + config: &configmodels.Config{ + Extensions: map[string]configmodels.Extension{ + string(exampleExtensionFactory.Type()): exampleExtensionConfig, + }, + Service: configmodels.Service{ + Extensions: []string{ + string(exampleExtensionFactory.Type()), + }, + }, + }, + wantErrMsg: "cannot build builtExtensions: extension factory for type \"exampleextension\" is not configured", + }, + { + name: "error_on_create_extension", + factories: component.Factories{ + Extensions: map[configmodels.Type]component.ExtensionFactory{ + exampleExtensionFactory.Type(): exampleExtensionFactory, + }, + }, + config: &configmodels.Config{ + Extensions: map[string]configmodels.Extension{ + string(exampleExtensionFactory.Type()): exampleExtensionConfig, + }, + Service: configmodels.Service{ + Extensions: []string{ + string(exampleExtensionFactory.Type()), + }, + }, + }, + wantErrMsg: "cannot build builtExtensions: failed to create extension \"exampleextension\": cannot create \"exampleextension\" extension type", + }, + { + name: "bad_factory", + factories: component.Factories{ + Extensions: map[configmodels.Type]component.ExtensionFactory{ + badExtensionFactory.Type(): badExtensionFactory, + }, + }, + config: &configmodels.Config{ + Extensions: map[string]configmodels.Extension{ + string(badExtensionFactory.Type()): badExtensionFactoryConfig, + }, + Service: configmodels.Service{ + Extensions: []string{ + string(badExtensionFactory.Type()), + }, + }, + }, + wantErrMsg: "cannot build builtExtensions: factory for \"bf\" produced a nil extension", + }, + } + + nopLogger := zap.NewNop() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + app := &Application{ + logger: nopLogger, + factories: tt.factories, + config: tt.config, + } + + err := app.setupExtensions(context.Background()) + + if tt.wantErrMsg == "" { + assert.NoError(t, err) + assert.Equal(t, 1, len(app.builtExtensions)) + for _, ext := range app.builtExtensions { + assert.NotNil(t, ext) + } + } else { + assert.Error(t, err) + assert.Equal(t, tt.wantErrMsg, err.Error()) + assert.Equal(t, 0, len(app.builtExtensions)) + } + }) + } +} + +// badExtensionFactory is a factory that returns no error but returns a nil object. +type badExtensionFactory struct{} + +func (b badExtensionFactory) Type() configmodels.Type { + return "bf" +} + +func (b badExtensionFactory) CreateDefaultConfig() configmodels.Extension { + return &configmodels.ExtensionSettings{} +} + +func (b badExtensionFactory) CreateExtension(_ context.Context, _ component.ExtensionCreateParams, _ configmodels.Extension) (component.ServiceExtension, error) { + return nil, nil +} + +func TestApplication_GetFactory(t *testing.T) { + // Create some factories. + exampleReceiverFactory := &componenttest.ExampleReceiverFactory{} + exampleProcessorFactory := &componenttest.ExampleProcessorFactory{} + exampleExporterFactory := &componenttest.ExampleExporterFactory{} + exampleExtensionFactory := &componenttest.ExampleExtensionFactory{} + + factories := component.Factories{ + Receivers: map[configmodels.Type]component.ReceiverFactory{ + exampleReceiverFactory.Type(): exampleReceiverFactory, + }, + Processors: map[configmodels.Type]component.ProcessorFactory{ + exampleProcessorFactory.Type(): exampleProcessorFactory, + }, + Exporters: map[configmodels.Type]component.ExporterFactory{ + exampleExporterFactory.Type(): exampleExporterFactory, + }, + Extensions: map[configmodels.Type]component.ExtensionFactory{ + exampleExtensionFactory.Type(): exampleExtensionFactory, + }, + } + + // Create an App with factories. + app, err := New(Parameters{Factories: factories}) + require.NoError(t, err) + + // Verify GetFactory call for all component kinds. + + factory := app.GetFactory(component.KindReceiver, exampleReceiverFactory.Type()) + assert.EqualValues(t, exampleReceiverFactory, factory) + factory = app.GetFactory(component.KindReceiver, "wrongtype") + assert.EqualValues(t, nil, factory) + + factory = app.GetFactory(component.KindProcessor, exampleProcessorFactory.Type()) + assert.EqualValues(t, exampleProcessorFactory, factory) + factory = app.GetFactory(component.KindProcessor, "wrongtype") + assert.EqualValues(t, nil, factory) + + factory = app.GetFactory(component.KindExporter, exampleExporterFactory.Type()) + assert.EqualValues(t, exampleExporterFactory, factory) + factory = app.GetFactory(component.KindExporter, "wrongtype") + assert.EqualValues(t, nil, factory) + + factory = app.GetFactory(component.KindExtension, exampleExtensionFactory.Type()) + assert.EqualValues(t, exampleExtensionFactory, factory) + factory = app.GetFactory(component.KindExtension, "wrongtype") + assert.EqualValues(t, nil, factory) +} + +func createExampleApplication(t *testing.T) *Application { + // Create some factories. + exampleReceiverFactory := &componenttest.ExampleReceiverFactory{} + exampleProcessorFactory := &componenttest.ExampleProcessorFactory{} + exampleExporterFactory := &componenttest.ExampleExporterFactory{} + exampleExtensionFactory := &componenttest.ExampleExtensionFactory{} + factories := component.Factories{ + Receivers: map[configmodels.Type]component.ReceiverFactory{ + exampleReceiverFactory.Type(): exampleReceiverFactory, + }, + Processors: map[configmodels.Type]component.ProcessorFactory{ + exampleProcessorFactory.Type(): exampleProcessorFactory, + }, + Exporters: map[configmodels.Type]component.ExporterFactory{ + exampleExporterFactory.Type(): exampleExporterFactory, + }, + Extensions: map[configmodels.Type]component.ExtensionFactory{ + exampleExtensionFactory.Type(): exampleExtensionFactory, + }, + } + + app, err := New(Parameters{ + Factories: factories, + ConfigFactory: func(_ *viper.Viper, _ *cobra.Command, factories component.Factories) (c *configmodels.Config, err error) { + config := &configmodels.Config{ + Receivers: map[string]configmodels.Receiver{ + string(exampleReceiverFactory.Type()): exampleReceiverFactory.CreateDefaultConfig(), + }, + Exporters: map[string]configmodels.Exporter{ + string(exampleExporterFactory.Type()): exampleExporterFactory.CreateDefaultConfig(), + }, + Extensions: map[string]configmodels.Extension{ + string(exampleExtensionFactory.Type()): exampleExtensionFactory.CreateDefaultConfig(), + }, + Service: configmodels.Service{ + Extensions: []string{string(exampleExtensionFactory.Type())}, + Pipelines: map[string]*configmodels.Pipeline{ + "trace": { + Name: "traces", + InputType: configmodels.TracesDataType, + Receivers: []string{string(exampleReceiverFactory.Type())}, + Processors: []string{}, + Exporters: []string{string(exampleExporterFactory.Type())}, + }, + }, + }, + } + return config, nil + }, + }) + require.NoError(t, err) + return app +} + +func TestApplication_GetExtensions(t *testing.T) { + app := createExampleApplication(t) + + appDone := make(chan struct{}) + go func() { + defer close(appDone) + assert.NoError(t, app.Run()) + }() + + assert.Equal(t, Starting, <-app.GetStateChannel()) + assert.Equal(t, Running, <-app.GetStateChannel()) + + // Verify GetExensions(). The results must match what we have in testdata/otelcol-config.yaml. + + extMap := app.GetExtensions() + var extTypes []string + for cfg, ext := range extMap { + assert.NotNil(t, ext) + extTypes = append(extTypes, string(cfg.Type())) + } + sort.Strings(extTypes) + + assert.Equal(t, []string{"exampleextension"}, extTypes) + + // Stop the Application. + close(app.stopTestChan) + <-appDone +} + +func TestApplication_GetExporters(t *testing.T) { + app := createExampleApplication(t) + + appDone := make(chan struct{}) + go func() { + defer close(appDone) + assert.NoError(t, app.Run()) + }() + + assert.Equal(t, Starting, <-app.GetStateChannel()) + assert.Equal(t, Running, <-app.GetStateChannel()) + + // Verify GetExporters(). + + expMap := app.GetExporters() + var expTypes []string + var expCfg configmodels.Exporter + for _, m := range expMap { + for cfg, exp := range m { + if exp != nil { + expTypes = append(expTypes, string(cfg.Type())) + assert.Nil(t, expCfg) + expCfg = cfg + } + } + } + sort.Strings(expTypes) + + assert.Equal(t, []string{"exampleexporter"}, expTypes) + + assert.EqualValues(t, 0, len(expMap[configmodels.MetricsDataType])) + assert.NotNil(t, expMap[configmodels.TracesDataType][expCfg]) + assert.EqualValues(t, "exampleexporter", expCfg.Type()) + + // Stop the Application. + close(app.stopTestChan) + <-appDone +} + +func TestSetFlag(t *testing.T) { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + params := Parameters{ + Factories: factories, + } + t.Run("unknown_component", func(t *testing.T) { + app, err := New(params) + require.NoError(t, err) + err = app.rootCmd.ParseFlags([]string{ + "--config=testdata/otelcol-config.yaml", + "--set=processors.doesnotexist.timeout=2s", + }) + require.NoError(t, err) + cfg, err := FileLoaderConfigFactory(app.v, app.rootCmd, factories) + require.Error(t, err) + require.Nil(t, cfg) + + }) + t.Run("component_not_added_to_pipeline", func(t *testing.T) { + app, err := New(params) + require.NoError(t, err) + err = app.rootCmd.ParseFlags([]string{ + "--config=testdata/otelcol-config.yaml", + "--set=processors.batch/foo.timeout=2s", + }) + require.NoError(t, err) + cfg, err := FileLoaderConfigFactory(app.v, app.rootCmd, factories) + require.NoError(t, err) + assert.NotNil(t, cfg) + err = config.ValidateConfig(cfg, zap.NewNop()) + require.NoError(t, err) + + var processors []string + for k := range cfg.Processors { + processors = append(processors, k) + } + sort.Strings(processors) + // batch/foo is not added to the pipeline + assert.Equal(t, []string{"attributes", "batch", "batch/foo", "queued_retry"}, processors) + assert.Equal(t, []string{"attributes", "batch", "queued_retry"}, cfg.Service.Pipelines["traces"].Processors) + }) + t.Run("ok", func(t *testing.T) { + app, err := New(params) + require.NoError(t, err) + + err = app.rootCmd.ParseFlags([]string{ + "--config=testdata/otelcol-config.yaml", + "--set=processors.batch.timeout=2s", + // Arrays are overridden and object arrays cannot be indexed + // this creates actions array of size 1 + "--set=processors.attributes.actions.key=foo", + "--set=processors.attributes.actions.value=bar", + "--set=receivers.jaeger.protocols.grpc.endpoint=localhost:12345", + "--set=extensions.health_check.port=8080", + }) + require.NoError(t, err) + cfg, err := FileLoaderConfigFactory(app.v, app.rootCmd, factories) + require.NoError(t, err) + require.NotNil(t, cfg) + err = config.ValidateConfig(cfg, zap.NewNop()) + require.NoError(t, err) + + assert.Equal(t, 3, len(cfg.Processors)) + batch := cfg.Processors["batch"].(*batchprocessor.Config) + assert.Equal(t, time.Second*2, batch.Timeout) + jaeger := cfg.Receivers["jaeger"].(*jaegerreceiver.Config) + assert.Equal(t, "localhost:12345", jaeger.GRPC.NetAddr.Endpoint) + attributes := cfg.Processors["attributes"].(*attributesprocessor.Config) + require.Equal(t, 1, len(attributes.Actions)) + assert.Equal(t, "foo", attributes.Actions[0].Key) + assert.Equal(t, "bar", attributes.Actions[0].Value) + }) +} + +func TestSetFlag_component_does_not_exist(t *testing.T) { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + v := config.NewViper() + cmd := &cobra.Command{} + addSetFlag(cmd.Flags()) + fs := &flag.FlagSet{} + builder.Flags(fs) + cmd.Flags().AddGoFlagSet(fs) + cmd.ParseFlags([]string{ + "--config=testdata/otelcol-config.yaml", + "--set=processors.batch.timeout=2s", + // Arrays are overridden and object arrays cannot be indexed + // this creates actions array of size 1 + "--set=processors.attributes.actions.key=foo", + "--set=processors.attributes.actions.value=bar", + "--set=receivers.jaeger.protocols.grpc.endpoint=localhost:12345", + }) + cfg, err := FileLoaderConfigFactory(v, cmd, factories) + require.NoError(t, err) + require.NotNil(t, cfg) +} + +func constructMimumalOpConfig(t *testing.T, factories component.Factories) *configmodels.Config { + configStr := ` +receivers: + otlp: + protocols: + grpc: +exporters: + logging: +processors: + batch: + +extensions: + +service: + extensions: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [logging] +` + v := config.NewViper() + v.SetConfigType("yaml") + v.ReadConfig(strings.NewReader(configStr)) + cfg, err := config.Load(v, factories) + assert.NoError(t, err) + err = config.ValidateConfig(cfg, zap.NewNop()) + assert.NoError(t, err) + return cfg +} diff --git a/internal/otel_collector/service/service_windows.go b/internal/otel_collector/service/service_windows.go new file mode 100644 index 00000000000..df5018c65db --- /dev/null +++ b/internal/otel_collector/service/service_windows.go @@ -0,0 +1,147 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package service + +import ( + "fmt" + "syscall" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/eventlog" +) + +type WindowsService struct { + params Parameters + app *Application +} + +func NewWindowsService(params Parameters) *WindowsService { + return &WindowsService{params: params} +} + +// Execute implements https://godoc.org/golang.org/x/sys/windows/svc#Handler +func (s *WindowsService) Execute(args []string, requests <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { + // The first argument supplied to service.Execute is the service name. If this is + // not provided for some reason, raise a relevant error to the system event log + if len(args) == 0 { + return false, 1213 // 1213: ERROR_INVALID_SERVICENAME + } + + elog, err := openEventLog(args[0]) + if err != nil { + return false, 1501 // 1501: ERROR_EVENTLOG_CANT_START + } + + appErrorChannel := make(chan error, 1) + + changes <- svc.Status{State: svc.StartPending} + if err = s.start(elog, appErrorChannel); err != nil { + elog.Error(3, fmt.Sprintf("failed to start service: %v", err)) + return false, 1064 // 1064: ERROR_EXCEPTION_IN_SERVICE + } + changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} + + for req := range requests { + switch req.Cmd { + case svc.Interrogate: + changes <- req.CurrentStatus + + case svc.Stop, svc.Shutdown: + changes <- svc.Status{State: svc.StopPending} + if err := s.stop(appErrorChannel); err != nil { + elog.Error(3, fmt.Sprintf("errors occurred while shutting down the service: %v", err)) + } + changes <- svc.Status{State: svc.Stopped} + return false, 0 + + default: + elog.Error(3, fmt.Sprintf("unexpected service control request #%d", req.Cmd)) + return false, 1052 // 1052: ERROR_INVALID_SERVICE_CONTROL + } + } + + return false, 0 +} + +func (s *WindowsService) start(elog *eventlog.Log, appErrorChannel chan error) error { + var err error + s.app, err = newWithEventViewerLoggingHook(s.params, elog) + if err != nil { + return err + } + + // app.Start blocks until receiving a SIGTERM signal, so needs to be started + // asynchronously, but it will exit early if an error occurs on startup + go func() { appErrorChannel <- s.app.Run() }() + + // wait until the app is in the Running state + go func() { + for state := range s.app.GetStateChannel() { + if state == Running { + appErrorChannel <- nil + break + } + } + }() + + // wait until the app is in the Running state, or an error was returned + return <-appErrorChannel +} + +func (s *WindowsService) stop(appErrorChannel chan error) error { + // simulate a SIGTERM signal to terminate the application + s.app.signalsChannel <- syscall.SIGTERM + // return the response of app.Start + return <-appErrorChannel +} + +func openEventLog(serviceName string) (*eventlog.Log, error) { + elog, err := eventlog.Open(serviceName) + if err != nil { + return nil, fmt.Errorf("service failed to open event log: %w", err) + } + + return elog, nil +} + +func newWithEventViewerLoggingHook(params Parameters, elog *eventlog.Log) (*Application, error) { + params.LoggingOptions = append( + params.LoggingOptions, + zap.Hooks(func(entry zapcore.Entry) error { + msg := fmt.Sprintf("%v\r\n\r\nStack Trace:\r\n%v", entry.Message, entry.Stack) + + switch entry.Level { + case zapcore.FatalLevel, zapcore.PanicLevel, zapcore.DPanicLevel: + // golang.org/x/sys/windows/svc/eventlog does not support Critical level event logs + return elog.Error(3, msg) + case zapcore.ErrorLevel: + return elog.Error(3, msg) + case zapcore.WarnLevel: + return elog.Warning(2, msg) + case zapcore.InfoLevel: + return elog.Info(1, msg) + } + + // ignore Debug level logs + return nil + }), + ) + + return New(params) +} diff --git a/internal/otel_collector/service/service_windows_test.go b/internal/otel_collector/service/service_windows_test.go new file mode 100644 index 00000000000..4a3024ba2d9 --- /dev/null +++ b/internal/otel_collector/service/service_windows_test.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package service + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/windows/svc" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/service/defaultcomponents" +) + +func TestWindowsService_Execute(t *testing.T) { + os.Args = []string{"otelcol", "--config", "testdata/otelcol-config-minimal.yaml"} + + factories, err := defaultcomponents.Components() + require.NoError(t, err) + + s := NewWindowsService(Parameters{Factories: factories, ApplicationStartInfo: componenttest.TestApplicationStartInfo()}) + + appDone := make(chan struct{}) + requests := make(chan svc.ChangeRequest) + changes := make(chan svc.Status) + go func() { + defer close(appDone) + ssec, errno := s.Execute([]string{"svc name"}, requests, changes) + assert.Equal(t, uint32(0), errno) + assert.False(t, ssec) + }() + + assert.Equal(t, svc.StartPending, (<-changes).State) + assert.Equal(t, svc.Running, (<-changes).State) + requests <- svc.ChangeRequest{Cmd: svc.Interrogate, CurrentStatus: svc.Status{State: svc.Running}} + assert.Equal(t, svc.Running, (<-changes).State) + requests <- svc.ChangeRequest{Cmd: svc.Stop} + assert.Equal(t, svc.StopPending, (<-changes).State) + assert.Equal(t, svc.Stopped, (<-changes).State) + <-appDone +} diff --git a/internal/otel_collector/service/set_flag.go b/internal/otel_collector/service/set_flag.go new file mode 100644 index 00000000000..61c155e3efa --- /dev/null +++ b/internal/otel_collector/service/set_flag.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "bytes" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "go.opentelemetry.io/collector/config" +) + +const ( + setFlagName = "set" + setFlagFileType = "properties" +) + +func addSetFlag(flagSet *pflag.FlagSet) { + flagSet.StringArray(setFlagName, []string{}, "Set arbitrary component config property. The component has to be defined in the config file and the flag has a higher precedence. Array config properties are overridden and maps are joined, note that only a single (first) array property can be set e.g. -set=processors.attributes.actions.key=some_key. Example --set=processors.batch.timeout=2s") +} + +// AddSetFlagProperties overrides properties from set flag(s) in supplied viper instance. +// The implementation reads set flag(s) from the cmd and passes the content to a new viper instance as .properties file. +// Then the properties from new viper instance are read and set to the supplied viper. +func AddSetFlagProperties(v *viper.Viper, cmd *cobra.Command) error { + flagProperties, err := cmd.Flags().GetStringArray(setFlagName) + if err != nil { + return err + } + if len(flagProperties) == 0 { + return nil + } + b := &bytes.Buffer{} + for _, property := range flagProperties { + property = strings.TrimSpace(property) + if _, err := fmt.Fprintf(b, "%s\n", property); err != nil { + return err + } + } + viperFlags := config.NewViper() + viperFlags.SetConfigType(setFlagFileType) + if err := viperFlags.ReadConfig(b); err != nil { + return fmt.Errorf("failed to read set flag config: %v", err) + } + + // Viper implementation of v.MergeConfig(io.Reader) or v.MergeConfigMap(map[string]interface) + // does not work properly. This is b/c if it attempts to merge into a nil object it will fail here + // https://github.com/spf13/viper/blob/3826be313591f83193f048520482a7b3cf17d506/viper.go#L1709 + + // The workaround is to call v.Set(string, interface) on all root properties from the config file + // this will correctly preserve the original config and set them up for viper to overlay them with + // the --set params. It should also be noted that setting the root keys is important. This is + // b/c the viper .AllKeys() method does not return empty objects. + // For instance with the following yaml structure: + // a: + // b: + // c: {} + // + // viper.AllKeys() would only return a.b, but not a.c. However otel expects {} to behave + // the same as nil object in its config file. Therefore we extract and set the root keys only + // to catch both a.b and a.c. + + rootKeys := map[string]struct{}{} + for _, k := range viperFlags.AllKeys() { + keys := strings.Split(k, config.ViperDelimiter) + if len(keys) > 0 { + rootKeys[keys[0]] = struct{}{} + } + } + + for k := range rootKeys { + v.Set(k, v.Get(k)) + } + + // now that we've copied the config into the viper "overrides" copy the --set flags + // as well + for _, k := range viperFlags.AllKeys() { + v.Set(k, viperFlags.Get(k)) + } + + return nil +} diff --git a/internal/otel_collector/service/set_flag_test.go b/internal/otel_collector/service/set_flag_test.go new file mode 100644 index 00000000000..f6f0c63ffcc --- /dev/null +++ b/internal/otel_collector/service/set_flag_test.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetFlags(t *testing.T) { + cmd := &cobra.Command{} + addSetFlag(cmd.Flags()) + + err := cmd.ParseFlags([]string{ + "--set=processors.batch.timeout=2s", + "--set=processors.batch/foo.timeout=3s", + "--set=receivers.otlp.protocols.grpc.endpoint=localhost:1818", + "--set=exporters.kafka.brokers=foo:9200,foo2:9200", + }) + require.NoError(t, err) + + v := viper.New() + err = AddSetFlagProperties(v, cmd) + require.NoError(t, err) + + settings := v.AllSettings() + assert.Equal(t, 4, len(settings)) + assert.Equal(t, "2s", v.Get("processors::batch::timeout")) + assert.Equal(t, "3s", v.Get("processors::batch/foo::timeout")) + assert.Equal(t, "foo:9200,foo2:9200", v.Get("exporters::kafka::brokers")) + assert.Equal(t, "localhost:1818", v.Get("receivers::otlp::protocols::grpc::endpoint")) +} + +func TestSetFlags_err_set_flag(t *testing.T) { + cmd := &cobra.Command{} + v := viper.New() + err := AddSetFlagProperties(v, cmd) + require.Error(t, err) +} + +func TestSetFlags_empty(t *testing.T) { + cmd := &cobra.Command{} + addSetFlag(cmd.Flags()) + v := viper.New() + err := AddSetFlagProperties(v, cmd) + require.NoError(t, err) + assert.Equal(t, 0, len(v.AllSettings())) +} diff --git a/internal/otel_collector/service/telemetry.go b/internal/otel_collector/service/telemetry.go new file mode 100644 index 00000000000..a6ce4c18253 --- /dev/null +++ b/internal/otel_collector/service/telemetry.go @@ -0,0 +1,144 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "net/http" + "strings" + "unicode" + + "contrib.go.opencensus.io/exporter/prometheus" + "github.com/google/uuid" + "go.opencensus.io/stats/view" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/internal/collector/telemetry" + "go.opentelemetry.io/collector/obsreport" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/processor/batchprocessor" + "go.opentelemetry.io/collector/processor/queuedprocessor" + fluentobserv "go.opentelemetry.io/collector/receiver/fluentforwardreceiver/observ" + "go.opentelemetry.io/collector/receiver/kafkareceiver" + telemetry2 "go.opentelemetry.io/collector/service/internal/telemetry" + "go.opentelemetry.io/collector/translator/conventions" +) + +// applicationTelemetry is application's own telemetry. +var applicationTelemetry appTelemetryExporter = &appTelemetry{} + +type appTelemetryExporter interface { + init(asyncErrorChannel chan<- error, ballastSizeBytes uint64, logger *zap.Logger) error + shutdown() error +} + +type appTelemetry struct { + views []*view.View + server *http.Server +} + +func (tel *appTelemetry) init(asyncErrorChannel chan<- error, ballastSizeBytes uint64, logger *zap.Logger) error { + level := configtelemetry.GetMetricsLevelFlagValue() + metricsAddr := telemetry.GetMetricsAddr() + + if level == configtelemetry.LevelNone || metricsAddr == "" { + return nil + } + + processMetricsViews, err := telemetry2.NewProcessMetricsViews(ballastSizeBytes) + if err != nil { + return err + } + + var views []*view.View + views = append(views, obsreport.Configure(level)...) + views = append(views, processor.MetricViews()...) + views = append(views, queuedprocessor.MetricViews()...) + views = append(views, batchprocessor.MetricViews()...) + views = append(views, kafkareceiver.MetricViews()...) + views = append(views, processMetricsViews.Views()...) + views = append(views, fluentobserv.MetricViews()...) + tel.views = views + if err = view.Register(views...); err != nil { + return err + } + + processMetricsViews.StartCollection() + + // Until we can use a generic metrics exporter, default to Prometheus. + opts := prometheus.Options{ + Namespace: telemetry.GetMetricsPrefix(), + } + + var instanceID string + if telemetry.GetAddInstanceID() { + instanceUUID, _ := uuid.NewRandom() + instanceID = instanceUUID.String() + opts.ConstLabels = map[string]string{ + sanitizePrometheusKey(conventions.AttributeServiceInstance): instanceID, + } + } + + pe, err := prometheus.NewExporter(opts) + if err != nil { + return err + } + + view.RegisterExporter(pe) + + logger.Info( + "Serving Prometheus metrics", + zap.String("address", metricsAddr), + zap.Int8("level", int8(level)), // TODO: make it human friendly + zap.String(conventions.AttributeServiceInstance, instanceID), + ) + + mux := http.NewServeMux() + mux.Handle("/metrics", pe) + + tel.server = &http.Server{ + Addr: metricsAddr, + Handler: mux, + } + + go func() { + serveErr := tel.server.ListenAndServe() + if serveErr != nil && serveErr != http.ErrServerClosed { + asyncErrorChannel <- serveErr + } + }() + + return nil +} + +func (tel *appTelemetry) shutdown() error { + view.Unregister(tel.views...) + + if tel.server != nil { + return tel.server.Close() + } + + return nil +} + +func sanitizePrometheusKey(str string) string { + runeFilterMap := func(r rune) rune { + if unicode.IsDigit(r) || unicode.IsLetter(r) || r == '_' { + return r + } + return '_' + } + return strings.Map(runeFilterMap, str) +} diff --git a/internal/otel_collector/service/testdata/otelcol-config-minimal.yaml b/internal/otel_collector/service/testdata/otelcol-config-minimal.yaml new file mode 100644 index 00000000000..372bd08d5cb --- /dev/null +++ b/internal/otel_collector/service/testdata/otelcol-config-minimal.yaml @@ -0,0 +1,15 @@ +receivers: + otlp: + protocols: + grpc: + +exporters: + otlp: + endpoint: "locahost:14250" + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [otlp] + diff --git a/internal/otel_collector/service/testdata/otelcol-config.yaml b/internal/otel_collector/service/testdata/otelcol-config.yaml new file mode 100644 index 00000000000..ac3bd1fdcb1 --- /dev/null +++ b/internal/otel_collector/service/testdata/otelcol-config.yaml @@ -0,0 +1,31 @@ +receivers: + jaeger: + protocols: + grpc: + +exporters: + opencensus: + endpoint: "locahost:55678" + +processors: + attributes: + actions: + - key: key1 + value: 123 + action: insert + queued_retry: + batch: + +extensions: + health_check: {} + pprof: + zpages: + +service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [jaeger] + processors: [attributes, batch, queued_retry] + exporters: [opencensus] + diff --git a/internal/otel_collector/testbed/CCRepo_result.png b/internal/otel_collector/testbed/CCRepo_result.png new file mode 100644 index 0000000000000000000000000000000000000000..a6dce048014b7a9884bbd246d0d11c6be6e1e43d GIT binary patch literal 2052738 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�-R9OG)gQ@SwJj0d0_pP#&wFF3 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
u(^PZK%vE{K=uV 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
`af*Dc+?m5 z`PWyf= zd;fUgG4nnB#%?j*Au+@WBd*MK-ME4nab+6M0NYra*I5E3nsJ>*-}@Y)sP8>N8qMXM z{MwV`%13QOu3lXl-8Tez0q!QgIBpydFM=%8+D|m9z#s6)>A5tQ<2L^VX0Uh2OGSnxEMtUtQL_d}wYzQiDWr*&$|>ZAE5=I_u+j zj+C{vH4ISV5}E3el4gkQr2UW>Il1B1`jcfgo`Zw#ck?1Lp=UouB$irR#sm3>_T1ke zJqtD2RFT8tXB=m$XnX~!(l8GmSnv*MzIu$_j!ij94#{kJuC&X+M+#+_^CY0aFm0rK zaDhi_9|Ab~YIkNUu;6T>)(65ycAqWMgdMj@G(%erGflroY;`@Nw8HQ9xL%ccTs4aB z0et~iT8~$oh5Xjt+tdC=LOH)WSYj=@GFRP(7iS&P&(*+pe%28ou9ui>Q*X}Ww4)Tz z6G2ya!$FP>hhrh~fG+ROu%#wFAxi!XZWRo2*s?)+p>*O}9?(m}`eF0c15#%1S#r zX33+_4c3()_Voo01z)7`Nx0bi7iJ?5(`o@pb7BslH-DsaVxz_a^>WyG2b_tLsW1%q z9wN%owzc;UXCCC!K-!=5jDwBwp~WG96RP_xod6AXCU!Y^JMRMg2|GA&H5vy@*(@h2 z$>HdWAeSB*V1aL0+Qc=kN1(DY{^so|pW+P6Q}8X*2V~8Zf%l#AT(8j^oTUgT}vj!Md`7OJ^%gUx&K@VBp-}WZlXZn8-3nI=ZlU zD*G3&v1}w`%Efa$F;Cx;N3bO#*b+8uDP}#=?P|eMF;W`&a5zOcX47SFBL5l|0U3DJ z1q?5PKbphcwqg^4mRy^J;5(Uh$jEoN1Q*gJ;t&oSHsEL;gm0hQjgLVBnFor8TN%W} zjkD$R^yl8V&)Q?jb4SS3Vu|$u%jR-;VE^6O4cAZ#v!exJdMMx;@mmi#BR`MP#RVZw zX2c}2R4@3u-w>4w`7D;31KXLa!O;@OEhW^K5aV?r=4<;FJ_t^gB5?5Xsx`oGhl*`#&x2#eTo*V>RTmrIWS~_zWy}HS5t>^vPZS`&zTRw1s-W z;r}1}PN@x~lFR-!Gw0&*lizjhL2n5(r7d-3J!?W`nQHU+CF7G;W1098Mzs^hmL6c= z%YOc4f`VRd(5~-!QomWTocZy0%x@}BxGcgVvrUAIp9~sTPUUAs6rJ-}uI<}C^tiuw z^MHqQF8wkW>ONN^gREI_z=k}_WE9v*IyrEZ520p%3KuF(*vo`R_`IOa`gn}bfnR5N z&MGd_H})7De6t$i^LX}@C-GW3qM4maINL*(S#HFsF(OpTh|2z2KlRIb)?6TeSEsTJ zE2|Qn@k^Q$8T(ORJkOVju@0ca?H0ZH(Y2T$Li4D&l^)3epIYEV9-dGQpXmIa7o$C> zy8{W7ycPi?8^S7?-%@?xx1h%87dB8_rp)4Bk6UJJ#X$M|U)k69uxolzl-3g!wYRia zuijYAyt2uX;^fq>WUVhAtxD?>e_sROWS(XwoS#9_DHkKxU)Nzl0a)GPu=T>W4By)a zidrC-0E0Rt{DSkMhz(ni%JZPS$TjAZz^cRb$!eEj1nBF)FY#hGDw#h`tE83LnM~?! zdRTZ{o)`SOGpaxq#h2pL{D8()z5*E6MPg`-hte z=v4CylBy|E4X`K9#v~DtHmndJJ%8pxTS>W}Ul`+06Lzwi8ZNHl{T@L^a;XINSYhRw z^a2K%@=cbS6Ry_}F)eRKi(38qg**}bc9$0dZNr5g|x#Hw|0{HW- z2M!et?}s<}bi%kAD}hj$!(Eb^S8f6l`2@ddiQrvvbk48lt0 z#@r%c5}B#Oh9v!&QUccHN&#LWB@yKkTfj*c>zSQqMWkS-pn^3AI_l#`jWp0);AXh? zGrNV&SJgo)h2#!EE(Z^u*X+mz>Tqk`euPPc980qx8=KA4HF}yI2)LtVFA7V6GX>a4 zLb|vIKy%j%w}z5UMJY%w`OK|IFW*P+BbBY{%X(a6pzD(wx__&+s#Le%(^AiV z5MW&Xh2=fE&p^cQ_5m}2qR*@VeUL?{g;KhATKvq} zkJ|RWDo5_l47h84Pz%Z#P@5W--_+5HvSsl$lG8pBAjMXWlkfBR`r+L$iD)fxQfbgv zNLhn6tUZL=&Agn&@4Da&8t%CJXH&mCX@l)BJ1B0x2Mr@UQKgd8bJs%G=%cT9h}sPw)S8VxcLn%&M(_IiXPT7(h56u zCz`s05L*bv26|vI?VWGCccmSSnn!uCN8pJ1*5U}v;Fn#a!*`B(dZ;?Af#WAKXkScZU}-d(pmjlkdvlLP{jA{FIe)BklMdQcl9 zn$)9lLZCl!;to%YvK<3ATTai$=H;ytN2HD`I{*cY)CN@j{_J#pQFFllPs5riQ=R<^ z{9R+@%-|08+43?9C0M;8yq$7bz2qTB@>yrT^ALal9Qmy)+Ra3@FWPm`7K%fTJgBJB zq$5cmMeaa_4VgjMB1569cf{>!yq;H!j>n({z=pS*3ZIVQc=Ci=#$phIQ}=xkEt{Dq zGuz1z>W~FY(l6f_uhi}DN(u80{hX9D&~C+q3j%_TWBe%`naZdEV5>#>!Po9al1~{E zbPes)zoK%Th>{WUQrb2w@vxqV5xQo$4B@JOD5G%W)?*KO7s)c$(Q?j3%t=FwL2cLqu%Tu4Gdt~AeI8ad z5Je+VRM7ofbnQ#c_6WU^c1{N%T3T?FdA0l7HT2Z4{TeSrFWgU+HD3-^mki z&*yr1`1@}(ECiX-@X)?tHGuN*{Ugz%+NKSr-iDhTorl92A6{>RNm z?oL1`o|abUOTWxzjNgGDk$BJGB|be3ct*ZQa{*^{nLL*t5S-#*CKS6w;8!ck)HKSO0?duoYSbVecS$1C6#GZh;T=&f)FOSO8tjGO zxIcT`BarT0Q@hgL4*~}h6Rl`KSJi>c{Bm)-j9wa5j>MvI!(YOAB+6moJPZQ09aeUP z+w6HdykNmT`+)+M3J|lBYWvI+#P*t*V7o?ti#j?u6l^6Id)%WPhw5FdLywTu@J}Z5sJ_Hg*%i09G!f=`klYz|3Su2@4 zia-$NL;spXtyci1yFAp0;VH1ye~flV>C5Ql_TFe1FfMfocxk-e{3l!(9h~ge(tDFI z@V1+7`FeN|G_&(IRSrSN?A&D$R^OM^>+!H3=NId@s0HcR^UW~whwnH7q6(ct4&R#4GH|$*J%u|!N-b+pWaYk zm$mxBTFbvw(mKDm*7JkIJv1~ldfX0VTb-PPz8?PE0X>B`$HQWpV2%KEN915<<1l02w_SE)sHFZY4X|8n~d-qoJa>sIR z3yWAYam^6{oy*4hR&WjCRB;2w2Q6k75bvVtZV3c^f8la!<0UJm%;P-(m~GX8?{a1@ z(~-d3>T>#>(t6%z-DSz0W%?=YL}ID0kB5HH^S9EmH2Q~%ZgZ*TN$AZ22kV*Y6-km&Bl2#flA!K0_k7zIVptE%AQ`zlCmQck<$Cc(Jd zKeu`u4j~wtZT>Ay_#ra!waGS(?*8W$7@_%A5aloHWt1eY4Y;&TP_MNoS52HP`(J%_ zjcX=>(gXx_7CLXX{`G5wqg(>VZp2-WW`3c&(;qe*XlpSzyh0v}^wwust3HdlK5nQn{A(KOXQUKANRzF4M@b!HH+gES}&(W8Zb zD<$1M9y8{@YV5xKMjgqLbi$Cv0!1WD6UawJrJr^ zMN?NV z_BQv5&)8XR;|q<04j1I8;uqjR#_044vdRuknJu{9yH}OW6t9SE&BFz_5vN$oImX26Z;u`w2d*@y)?5rn(@l>-LC@E15 z=2r&3Kdd+un3r50Dj_CO=nC<7nA|g+RJ$N_r)ueA`E0$eUCG1PZzNlqp^YF6b9iM= zj3!<0J;;R1Ec4Hag;0$asImXl?QW!tW7ZAaI-@42Yyozo-cddWz1yi>Udu`6$OEEb zz4QAiuRYaAP71-uz9!@)(ZCtRB~vEMg^*cZf=jbwwU^Ts)eA0^&S7~8c197tlZBXl z%?w$R*@3^gRTZ`O9=#5d&H6ER+af3ubdsj@7=yPdsKUqgG5@m!f`mgTGNMVJ=@h!v zxuQblA&l}BySIU0oa((1+?v~=B}h24y&Dcy+Ru;O-h{O>@O;nUX+i%zc-pXW1%|#F zxP9~^N8xBg)hZRzNWZr6Zt4mDABt%Q#NXpz2hwF9oai)Jw`qg8W|+2VX$XF;v-&hMbN{@06QLeK>=9sJoF*{1#tY;`jGGym7qbV`;j699^-lZ+f|qS z;Flc$A!=Cdz*|KHu|t2kX8zcj zZD>Sd<*N>UvPOf7HM} z2M8?7rFOE9tvX=iv}N&}x&axjYtK%f0JTp@a|cY}2$-pxbxfO_7ymF_v&v$?7^T8) zSCH=EB#lvzs0KS|E6l%~)5mq!?zg@*%eQ}R(q}_G_Pr1OcmMF(KfZxMkmlL4W(!xq zjLE!pxn{TvJk4@56{OnH^Pa7iha@_kq$5h6%&INqR9&a)^;`xj?g|toEAkUC6sh zBW!e?{vyMv&EW*^FJa)7qPPpX5e*Hz`XK~WNlkMDw>KR2{&LeRQmRxrVhD73v?dpZ zA$dsZXCE5nhlx#^B;E-lbf7;am@O^Q(se^+MOb~j$+ev3&oVf7)$(?}7UW#8I~pG@ z>xi62x7{9h=t=~l%@a8LejIUO}p?3=9CDqHre zb80aUJuu{hJ;&O7?wi1sz3@SU3cbDAQb+p%6jX10>q1pniJwgZ&xihaln#xQZa7f- zE6AErKC1MxTcZkjzHU1iO+w*|>!UHH7(#|8eyyhwf$68g09_?xJ~;LD5^&0E*iUUp zEd7oiUDm{_Nq91^_{=m}Yx(>M;aOwr%#${9P7+J4*;Zf<*^1 zo$`$gKSzeC@l8ai_Xfgs#*dWpnj;nuv)1MHZSv8;1N;B5_f}z1g>T=eiiCs+NSD$b zN+>l50s>OfT_Pz+OAg&7AqdETfTVOv3_XOTbPXllLr?9+|NY**ziS`wYwv^o9&o^{ zm}jkd?kDd17wa}HWnrVVP=boBCEuw1+4ThX17VHS17^wN-Wy%<6Cslcjh9&P$cq$( zn)ZhWtygxc6Hxo3;YGl>buFiZWr>Vc@iW~x^sZMe9RT|;U%8NU*%EWaR$60NS05)Z z2cA9Mn6aSFf5-*Bx!SQW!(J!2ciu>ysm+7-MlVJ&)bPJfn8MjyUKq1s=Lfgazi9}_ zhDNnw;q{DByyfp|>uB!>Kvvs=6Mrpi%%j>G$V`u2BudA`Zy|xJN+@CUlBF z|6Gf2!Lz|O9Ip&EF2Uc71|FZmd|PWHe8L?ueRvdQne_K44+v@^rO|^m(3}#f?itwgIzFB!HcVnMVOg=Q-ph7T{Ed}3X9ZTywg0>*gJ3x1FQowr=J!#SZ#uX8p`t!WH*i*{Xt zze{+{+1R4?yJ8_jMc*R)9Y-|S<0aGf^bSb`cjh|YHaM9rX0O3VSNiRN_i;kw?dTQDS+riFR&F;#j= zRk6A7Sg-Z?kJYP^?_M=5=(A4Gy|;a9Q4^bUXQF8Pq-c)h>Vf* z&Ht5$OEAe1Bl&ASdZ9qi5Hw^SjanCN@tih;?O9P$?voA@uFouM8P{)(B8a0EpmwfI z=C6-l{W5-C2X&AgkeYmOZ&ms}9c?LuDsD_PG|@7Dy>4V}sevYXL5lAz?~h8$`#KXF z%2%~~J{&*t@Ra82T?1~MI*IlJZ~n@kNSV8qc^ReszI$i~{t5dP!H`k`xFkKZAw+nZ zw@8v*Ro~et>-+A-b4qmCva;mk>oiNjd?wy^$m_9~Z1*;{81h;~Nqye4==)Mdl+XNm zG!=M^?EN~7b=h04b+7lL)ODSSc56A~>NkLQp{L5--_(6=U5d6JXXhXEp_Wup>mA+9 z`Z#TYV$1Z-mJ^>@!Ie6Vr5(u4`75VZ&BtQ~5*bfebTdDUNC zNhF?0Ium>W2ZNXE`T2e-C=osrQSFX@wHS11r*IE<{YDk0A^B>Y%*VrH-rV^*)^Gnr zY3a|m+Qs5=@AWN#q8qwSJUVb1%KV?+bfTVE##xG;{=t)G6RI8`M-JTb?LD5Ba>#p? z!)P~<2G-mKnFSz0(tTk0DfXAo2y$)zN$T^o8-N`D$>>dE#kc*!&+9v#?LnbAZ;CEB z(H^gjv6zKu%ve_O?J(K=aXGu$UuTP_-`*j!QU@c)}WF~|vH?Jd6?4!_YG%FPR?{f5o_nWQ_xB<;KIEY(q^K#S$fo}P_ zH0s~Os<9E>+8L%!8fo-579``QLqTo&glGK3JD~mAOJKwE>0YF8J3lgI$#uLdjzr9W zISH%@$p`ODupcsCqC%BPrQbI>hA-hq#RhIakU3t#@fR6m^hPlf`Q0cJ(LsE7xy4=A zUN;W>%(CE&kco195l}BT^S3Oi#VoKj);5@}CG=XEEoyCwwRzy_7nc}eZlN{iIWuro+*`(4 zjUm5rxZq^TJHA8qb)QEhLr4xomhhDyeUF-@qi~%%P_UruN>@s?TD>|mn9ch=k1{QgogEx8ppcn|61A8kQgx61dBG z9^tjpOy#N?+jI3&=%Gn_8QZ9tNZ@Yn258jMo)#}hqBC7k4MnnS?Y&Ve-EouRNcyBq3 zox{8r{au4iL$e{7TSMYk(4j=iyUFHvvj+HK%-`}+uN35B-`ZAY1DafD-rKT%HrT`L zLNM|gjcfq{PGF3nOqJt+o%wza(3N3qQhMptpLPY-)2)Kj-gmcsmfF?C;qW&TXmPtPxv ztn2~ltM&B`b*XWL7MNHtpw6V`J@=4784>`h$M0MvN@89#-t0Oy=~h2lXMXX4J;?`6 zcDMf3-Ia-}<_exMD#4QC!2JQTER{#Kezaxf6XZ(M7On(=3x0sRWZ1+uFlixQlx{q| z1mN!~=_P=1Ni+=d$r_gMZ#|#t|MsqHC~8w|1%Qq z;tjxIdml>1FM2o5%H`mntSO05MRno_C1oaH@Ag^ZKjKTWkF-uabBO+Y^nU%W(=>J& zre#VheJTW#MC2}c>%Jb#6jX>((NH9rW#`&pJb?})okpbdan0-O!UaBDKBM|$zS1WZ zlut>`=LTSFnHp0(kl$L~knlSXd^M(BQ`{a)3&IAZn7D?2l$~>bYYXKR76# zXaKT3+nR3X`!WtR7KYDfHFP5?J$3{-NmgM;i!b>siD#FmD;RCr?D zT`V=tUPomk;Bul;AT}z;#400;v>;J#KzJ z3Ke=Ch>hBL_0!~t@!bHTa#Q^pnIf+KPAELN)9LyWe99Ox=)_`M^14J<&>uL@qaYyg*c-q=LYTC|$=5RpRP~`gf&mPVlPhj&=P5 zOT@>@-<@h>&g0+yV-HCPk$ovmg)7hV8fs<# zy`MiD;15j2oK=qe^5K~?r?Ci_byhe4Zxu^^^J)xkSWg@{7P$XJdCav-#-aS^(tl0QZBm znOgfd1!P~VwCFWH8(E`ZMJtAA|G5x2>8#pi($Bywb6UZhQm=yE2_8 zmaK}|Lpr6{tYgr8Uc|3_j(0W_&9AXCeh1yLW$G_hKVNdls3UNHUlUO^1&8AOW^90W zntBbehv@qh$G2J(EC3LrrAPZBHBKe_PO`6ETCX{Vo=zMctG~U&vu$4H=Pfe1Li3~K z&|4qH1ncLI3d^;nBEKg)xh8PO>)39`?lSej^)a3K8A_TIL5l>uHkKE?SiD?5BjUx| z66E^u!E>%i#--l$@vEqm=1{(gnSfu1(EcnrD)TMf0Tf8cH_t29XLG8{{7a=dO1k7e?fJxabDea0zZdDvzHAr; z#(=N7nLih0OINKE9kqAHH|dUpCzLsx^=3UCrn3#50Wh>>b+5_l9dy8K^mY_$qQ!FP z!xtUDYDXkauB{p4>8_|jAb!J)i^Z@9hdK-(MS13`(7wp1!oV?_l9SIdk>Z?pQ%?j9 zgy19$O}wz~!gLcJ(}x%NcXNMulG|FYKbO*R0S}TIJ4g4b7I;^-EvgDmtTFxIs(P`7 zbD07`^{7E4mP9`;6IV-hG%8n9pHs{{fI(I`+Ex+d9s;?ob-bq&>XJ=>l}enwo#l@< zIgB=&Hz6f|b%sb#Ac|EfNATL|_|ZnK&BViM7ZylcI5XYxP3Gtuf%#0UYS_HNnDR`? z?7$Am26eJ(GoW>$0jo|}Zyt8e{kaYTvz>A#1T%!>6Q>K9<4LUXEwV{b+&q1;jW|i4 zWw`0K=^I^thtpow56fK1vIB{aI=TK^JN@cNGb>8EA8H{-DVqi06NX%!~0Iho*9?FPBj@ftwPF*MduCeHp zyOnx&!z=`NGc3RwzJ#=t-o&BDdD0uSe#M}rZ65KiDZ9yIsad86Ev8AIm&Dhid($SF zbZS3cfT~Fk8N_9c}in+uW{Mek+60R@Cx#okn>zC6nkwm zUN)nsfW@F=y*@5H%bBle-^~n*=VpX!wc=F{;BE?g++-Z4W>`OK^JYbxWKbRFP+F zVWhm;K7vH@W&*Iwvt^IkA2WSzg!{iAZOdF8%O5JuRG3nrJ3r){La->#zXvTx?ZN3m zJAaF)XZe-;fNJa+JfZIrC$2KV^}yc0vj1xESv1%uK>AV}0y=m9JM2r{CZ>W^uJb@2 zM}qjgmTb~PZd9daqYGw%aq6U&2bHQ#S{9&1={We>x-v<4oV1L zlbCCOH=qe)a01u!mPW z2IVo31V#y}AC*7tmVdllFNqKIn)B3$oBLp1R8-97!XjP;LIcq;%WZyJ~OK<1JnD@ z8SSdBiWfKbv@|pyOpbz|`p8|2hcIgcx!&MhY^md;qloYK*Jg99e$`J~wIxcSK2d(B z;=lLl?A>_rPcnq4vyQP-)hgbq&pYPdUVC~g8K4h33x6`7OU98ZMN~6ZEvN?P57PkN zwBtV%glz^NmnE&zrma~T$6sdNQ=$(4%!#-A@6WgAgE`sMa=;Y2OLV&?k*F~!=x)b$SeYvS0KhAi&B7ej&GhBWl!AM3^y z8MHSl@fkkdX^d{Kdk$;|9yBNSbzM`Om@lyH=A-O=9~e03fq4LIK)z!9W}u~Q!PS76$5(F>GW%k9kRYzQ45 z5PlQCmL6aDbScOH-ObNl)-+dcYy77Gl$>Qc7x31aJ$bwq?UhDsXQ!gho#UE}T0aXv zH%5Yi@hdJ65Jj#5RRmu@ki_fTviR-r_B4E5+==%N-2kD3eG~s+0z7L}vx3i#jkmav z%tp%)Y7lxfE!1bm`;hJ~a7LJcPQ(9v^nZU7X|gj(4Nrfr;^S&`hv(sQ)kkT+QQdtl zY9L`}9FR_*D-}ZYmq62jtSIR#cKGJJdyYP3`7A=$FPtaHj{8RTE%O88l~5K;VawA~ z5tB)3R;7gZ5=Nh&@@w@KkLMFHtoI(U#e$E&ki#e`8<)JY#(%Dbd)KYzlXlk$MSY&J zZif<9&s1i!y=~fyrBIjp0($dTsS6-gNsP0KD87Cq8}#v}yxuM^|$k;iU5HR^1!l2C@<=v^vvcMcJNe81Bn|2^Wv> zvA2w$cwiqOJRFw@dAYNCLc(L_%brjfY$up? zeU}bM$GJjRuqHQwU(YXk<(fA_9@XIUZ$D+$THf^JkEtDzVoeTFKO10FA{FN67a@;J zBA2r43JalP|5CnJ*&@*2ghymo2P!Qhp^A2@pPKFkM4X8BRqh)J#>a9QQV+fLMZ`bR zFs?6%P!Z~t^jciS7@)H`F}}u0oI!7zwhqCc%*7|eRb^FGCv%g&mVyC~8C?FZeBn|V zqwJckoKPZ$Z>hUDi2b>msz^Jyuj9Mw1?QGkLy-?=gJpG1U#QBWbBfiT#^al{Zb3l3 z-5sw&RZavL0J(^@cFRaQ#?&3{0M~93@!U2%nKwiqP`G-l6-vx-o`xkaW4_F2jGT30 zMmyjsa&T-Bz8eEO(=pI_#k zq#d?EV066UW1B9#!2Lx-)po>|s>oIXO(%>VO^p< z@T0&>Nh+7uex4-4(VcwMDRjO;?_yts*RfqVRC#nX5OP>rq!z7qjp3vk^>70>)elw3 z`bhbm0Ikqr;p0w5 zND@k)jg+u!Wji*tfxF!1$%Sl>5&=AuMaDjDUBIT2F0->n)qV!g0nKwFuM=7o3)v7~^W&DsH z>FZ5teesR~+U({X7v)$>Y!(QdmxY>Ui!|ji@n%5RsvtJKxF`d@E6OR2j**#oydJ;B zUst_hJ?{OY$W{M!Wrs_^-=%My%d>(kL9KR@=dG&|xtS$@n%+>9;MH~tz+`Nm=ZL=* zCvcseV*66XaslK)pWYZAWzI~k`t$`VN|zcAMkQ+Qi;i<)%0Srg7xVvy#m&8kj~S{6 z!V?MK?Pw)V*{`O}fLsoxE-S&He^q;l3#}T=Gm}!@$!}-GOW=cvz6Kwg6#T=SDp?jk zp=v*jyw3SNYK`Y?_kr-fU2arEdA~g4|(bvDx{eeD3q%?o3yy)E7yWAJ)G( z)O`O=O^dlYRxT}xy0j09OJD)Qvj64jkYKZh47%rhHn+%_qM7BQED!COVBU}R9}<-_ zGqPpewT*T0z?iFC6Z`yl=+!Im_9>)3I7{ z)vB|Gk=KyF-r^PkPTipCiA;thOaN_2cdO$xb8C|&2aIFi|^PGtA5K(QIVrIx-$mi zO%yC_5090_HH+ocY8Z}EA5-pqMC+XN9zBPd!-`1x|M_IO4qJ4MkCHu=Ek7;)PXlNq0SbtzkZvl<#Ajm*9~Z*(})-lU*G z9}=-^SJ0znza_*dbfw{H*?Vb3@*wqwuO;D}nG|2=4mmU?g586XZsA@swh!gk=@QsY zWJOBr6=nSKT;Ll0{PUMfT7hz`0cz{&tIYrgU%vMne+G@L5JeES?`mlMGUp2fd;uRGuRo3->T z?-sKoJH74nH$E8{1<% zcJRntEGsjv&L?Zemk#u^tWW%P8uvpvjsCb(MX)nV@8kMJ>m(P^@R&b0 z3xFa^Y?rP0f)=a?xRT?=t7GFdti}&Qj+BiPKn?0uS(zeR%tyQqN!eh1HmsQaVt>8k z!U*6=x;!Nk(=c?Rbols3Tyo=;{s_n^-GbKhTQ;}{^ha_lrpLUB)wL|KI-K^Ecyk>; zl6n6N92yGWxJtR|zlL!VdIC*PLAadZx+S2HwhpAeZA3mg#2eOcPJW7xLank;m{(sO zqZLhg0QqUJ$tSljM&tHmDQv+-KO%9YR>pIiLAAZBSPJVqqVkKU<$F;Ufel<}WV@b|?KfS}VDfs&Ju{ET^lJ|@2# zPpg<7RhY@r{Po8vDrE}}JQEz-<>#COMxe5`;8NHLNrg~-+`#c9pB$C1Nes|UHe?RLQIN*c%vI5wJ|JVJ@2k}ULswk8ByNv0{ zy%AM#<5#HuDuvy=txJ=e+zvy)iO@yVkmSXckQ`YGkD85xPDD7@vi-Z$bb*P9G)N8C zgZnv(zc(efXUJUDG|giExB<#gw#YK^u|4&jK@2r<^123)hfTb%Faq5mtqLC{OP1VF z7mW3AF?`BIg1?d0O5ObVHF_BO1I0pCh0bc&;UM@ zoTX1LNQQ&{(>ddweu0A!$Rc0)K&+1H*kUKpVRckDE?tg@uF=NCU%=Shu^|3d^EZb{ zei*cTT-WrQWS&(7QeQ;*KRtVwlN+X^t#gOs1+bS;BU}^L3Jg(im8oK7(f+x|d7)DsXzE?Q8 z)~z(pR>kKe7fN7vv^C%M6Cmzq%T+7^Jri}RY*sd{vF`$oB~sWcw!yE}LaZBo5#9Wwp#OKy zcCo^gaPRI6HGHOp?jl4 zsT}&!>o6m3ss*OgU;ozac+c(WeM;Mb(adFyPzU6j!-%HFQGl78lLzN@`92kh6WD zCE*gl$;XY8YdYCzMGyG11H-kjHJ@;zy|w%4?dVW|yQx6El?k-xCz%y4fTI)pTWsTosn;90PUm|RAitFX zOfGQnB>$x3r1nomzDHhZEZ^Le*<(bP971IZGiokzXn0Q zX|JAr+YRRj}J~0*Ie_2`tzg4W+wB=$gU#u1on$BUY@Ewrpurt#uOWJw30uLp-j;Qr{}6VFqv7TzO^{Kla!(fU-Jo5*tl!fg9$)x^5(M=OJTY z@q#xLy*OE-EpPV^g#bUtzWpvXEPb*;0T4Jpf1SfZ$FM$T6+$ro1q|rlLF3ZZ$@-G9 zOB?%Nqd;sBWBd2ZN&wyPCqDFmn%^iD0cq@LJiuO{!+$58V*$kc_S|*O7BIgY5d_e$!_16Vq@J0mE{B{?zdAk^xzW2Z8cm&FLXQ^ZJ0lLCv zi|D}}+`J~KVi4A(y|CoS=9`ydFpd|LRE62i#z*ZXp6RG^67^gwP zf>dQHOsPB65M=K&j0kfN?)&s2gJVb^@z_j7@ zO)L2*SQx=i;=C3~2U8!z2A?2Gu>gby*@DmL95(#exp-xB-FQDjQ)8jq! ze9*2Y7P;oq*%j`5efrPpd7Gnm$rD3D&>XkV;;eO`I4 z`I4^<0?QKTvhxnY*}j2tAG?d+{PE`F`XL}-)ibj-3L4r=Z_xlDQ`eJ_DCT2mPv02y z)9O9(wMDYSR>Y{cLO{U&LZb!Ph=3Bcp*oKp`U65NL$=zX!(z@Y2Mz9m?Z+Wa(~ZTD zkc}*V3?%TT#Mlh_wy&Q2q|pN7pOGRqa#r5{Hm33^W<=5@jQ-97#O-w8(XV@Ldi82f z%9HqRo{XvUaKRX;Y_SAJ9;1X_k+DUeaAU!b!E05^mHI5042=L*BgLK_({6So_%n9J zfCcH_iOO1sOMbaA7y$`I;iX=(05>~PM1&`A0dwV6z!QLBM*k8U!OgLeCWUp3h!@<= zv__yPY`>u}yvnbE`}J{Bn{QmFlYD`KKjR03Hm+x20EsDIaJxJOSc|Z?8u#aU3M;RzW=y?2iH=X&{KvmRaPj2czyIK$ z>0LIba#d0!ZAHMJc69%?(?<*iUaP&x0l8@FhyzwA8#M9e8+*p2nXmX8ff z(;u5wBb?WIqU8{{7g(6|WfR~y&eEzujY4r_jO(2bd@uG_#g^@?#Q_1|nRa>PcDCa4 zf9XFAa<0HBABGq4QqA!mc8ylBx6gj_UJitP5B%AK1dP8;wXe1n+mbk+AQs(6J%{%) zUs3qx^BK{h?@9MqIy3cz$S5*mkV_sdlGw z_9|MP-Ekmf`|FdpxVcj<%He~5d1Al5s=D{Uvvh>e#*OvP^>JSs z1)r#1_IHy?&i59p?dds}@i%(i`o2pZ=i=0BcV#VVqvsQ4lYxe>Y%~@%#rvIi{isne zc;sEVKRibLbZDo}AklmJlhxUWYdT4CzF z5neY>k|v-&%O}&-D;vG^7T4keI;rQtc-vP?|9*RxH-bq=3&J4L2_jEKUfT~4|F;%E zhU^`oTk`3?N#XLB_mhw9rH8s7c-b7_(b&zXk5ZAyb&W; z5H_pKq9U(2^d@ILkw3|RcUCBayE?1n4oU(+D$})Ay5$%8ZoEB>sPp6KOp&=X) zvHHBN>IT=eujfYp1gdLP5z=0`{=ho_Xz=voH<;gAG^gJFs{Z)sy>-nH6wnG*Xg=vWls*#5JZ(R-0BqDU&N;|Km` zXIIXc^MNI7^u9$qAdH_8xu(9ZasF=3=6ou)+8P!mRYsg4tA7gaRShItNWL?rabh-; zy=Ll05kIsGw~FPDu}|fUf-Mf`zb|iK26vq}G1t_(-M=RUU={roj+Wbe{7e7#?eXyI zaRX?ghU6>C31(0GL_Q8WH5l__dx}9o@DLwqP4@0l70Wtl4{@<(Z{N+<4(INhlGYdm zKP3U(kHHLs#v5QRwGYGq4#EN)P|pOi?3 zV{~c51yTv|KUHRYk83!;VWT*n7fN+Lsjcx;ChPupPpP8b>&qSrFo*x(hH6ZtuA~?7 zDCCMx)Zy_)cEE)pxA|BF`gs&yzNSsC?pW$AHP-MyY_Fh{0>TnoO_}_Pr=@Yx=PEab zshrWNTEQ!&6e1hk)>hJcmtMLlsVct|_qMhK|GO(bw`tj|L_ zLYneAv=n$b4&H} zdj}kUo3yOG2iU1wLP=$sb`cP9ZzeCM&pJ$!Hy%jF#*{ZE)uA_B4^&%-_>AgiS6P3z z05Z~x4Eu7z);AveF`wccXDWXA0U-S<#{mLQo=R$eeAFy|&qLxiKbKwS_J1G5y+=$U zab;Iprt2NyBdgt`}QxP9Wa+2IHbQ%)?qD5mWfKtj7Nd)tKsFp z0CoPu@so~t)>ae)pm(&$p?BkZR$FedKX_qVDEy`xM^zdGZe`5dLBIsbiJ0IE+RId0 zvN3~nmqj47;h%njybs^x><{x&Fx+_~8&l`jl{6Z1NXFn7F{C%Nl&_USf(WIfX!1(S zp;Rq6j8glY&H#SbORdWIEdx>0kE_Z8EaU}MDYaKm)9E1Z^~*Y^<`^2C76W#yRp%F^ zWCB4xP22gj@e(I9c+D)}T(l8b(KfTcsM!S`<`dAjqj%hC{O|cMUwzX&&#w{oDyJod zGKmLfx15ZMpN_~LEYy1gIIB;9r$%hcB?PNud#9f6&7^Cf4+eK5{lVd3OF*Fg;;d;L z>KPzwsKErmV(_16SfnQnKRQa~1cdO`f3DJ0J61h6Fy4r^g#=80k2E!W6Q5CfgR1@1 zdGFHpiesF~wTEmwiB8|oFf>0x{T0Tq({eno1qBFHOCGiF$@pXCy%#=$JpmM*D}wq@ zq#(ooHi7Ks#OkjgOtG4imNGPhHv#0Kb#Sx=niwv{%HP=VX=3=dp~vFXHMAE!3Cerr zEn*98nHc0z3$x^#j~f+DgszjteXb1E&6e7teq5{{sJ#B<0alU#%T*eE21SqlJ%nq3 z4srqsK^@~J=bo+D6`MckD;UU2^(&tzu!*Xtm-H$}-dA#M1H6q6qq+wK88;VVa}hh+ zET0bz4EAoxxmFfTZoP_!B|yauU@UZ;NS~svv=vD~NRXVH`U9z2L3bq6f6R`0utxLkmQlQP&c#OSzk zyI6s~1RQq4qAod)*meP+zbbJuf^k;;9Po5+b^@@h)t^>F z!t{6nxREtorXoC(MN;S!{p6QEqCoe%jov#G%?9lzH?jwRe~wy6oqL{cL`aI8e7buw z^|ej-J(cT^_Rd zvZIc0_LJl#J#<=hQa)C~zBvH3VJ?~85z4d*6%5E>lB?ms3vB~qqBW4;VGg`{x{y&z zp9810&G}ZGyERq6M~8XYYy}TkOORHvJBMOdk*@xl)FRIwISd-T0XY&fboVqxJPy+< z{~B!ixfKmUt)Q*Ch$v>$zY3W4IR%FXYpUXA7}*f^PE#L^`av^99jV^)Cdh`fW0-=# zcBqfD`L-t;&>TKI5{58FB@@@{fBG~ZNjha_t0QuH0k1l1Kr1&<=BBvg-b?3}wLxCB ztnDI3KQ4^ewk04-+*oIumV?#Re@Yr;3oK`ePE#?!?8`3(_jhN)r&_uecHm`{g|!Tv zIII6VaSwd8C{}8((<<(n*^jpuN1MvYCaBin)8&}{**ofT%D>gIQK#~22zH$r)8SL@ z0&mX|+62kH1-Nzd)yM~4r1{_T;zTnrb6Xsb>l}&CcLc%LMCp+?dRLL9)eF)DTW*6IFBSEuD7 z(#AvvA2TN$kISV?p?0;b2HfF14?_}L7U~oiuf6HCg$cbxxmWJ2!=w<)z$%o^15Z3T zexajAcwqT@Rrf<{6L~^8UcI3X0jWY1wb3i47c>O*vhVPCw7K+#Qp+X~wuDLe4}nW4 zEgaIgAD0zc-pM#GGo3)G_RUb;mE#x9Ez$j{S^hQR=>kAm1_WK6xS`l-|0|#-?pytz z*C3$t>_~`Fi1hvc#M(RjXZ5#FG$C~752nZqpyzzGAbrI?NlQ>a8T|IwZ5WCW0}JyK z(;93E`>ed?f12(2M7s6!Pw-w*b4{@!6L$f_$l#QU}MO`4Wq^kYVpj zYtDPW2QOes%6&6UiWO=@(~~cob5n8_t6>bWj6?#ioDjE6{!m>CIXVB7j9c05prR;Q#h>SyVmBJ@dWGJ9Uq$BLa79wuM70rY%_O zHKJtJN-%m>^x|uj=_|fmi)b=G-1`a=Z_*!bfj@k(9nC?sQwy&_GWG|h7e07-LB4;J zzRdBYW}denn+q-WiTzslK>ex1WHH&tX|LzM&yrOMvT)xCFX|1p`gfdBTK(5E zUjx5cH&JKkcZ*)5#W_KA9|z;3U7Ix zlft1vwNEGJ^p=EPv><|ne%#c1Ng;(p?MK_iJkjiQ(Lux9!`TLQ|JUgl9!{;s!=;YD z23NFL#g%`7f#`EK`voRN7oUXd$!s8Rkwz!}Q6*~{c10(ir}oG?<>6|+ZzoDH^p{ST zJw3JA=cfU2e`~y!?QM5rN7B2e<@YJh{!yE+gq2=~T>u#m(%+hLov>t6VpSW*yb1T;xE_@Ho=?-pl$Zlw-)g(P zi%A#(&AjpKywZ@ki^1i5yeb+gBZ|E@?NU?&k>zD)*U3VMh1HSTb}i3o5Hm^91X>lA zZS@b-)9{sp8y90RPvae%QpIe+)_rk(4N;zxc>#VBz}DZ;ZJy(*0q$%JIJ}x_eSIWm z`^HrERa0NAy3`4q!*cmcbySJsl*43hg=veNKKvd&#@$pL#i^0WA~8ktn_U9$&rxuo zdiIMi7xR9C*(MF#A6pK@CcmB|Vt7r>O<4P@_ic_T(KnYAe&y!S_G}T4*}TZ{um=Mt3N%+<(yoFp8VNhNvJgj5ScXIZ1A1hty4`rEEZbRO#V6NQp0F^ zU-IUWM3Z)-v(a5DnJwN#FcfD^L)c}%Y1vmfarok_YL0!3eQUPL~ zVHHq};%<#;y2l%5)PCX%KC{zMM^dt$!29ry)gYXUq7TEF$`YxSzTd~{j((1}`oNLb z&#zNB0@Rck!_V_LDoNE!yng+zd%5{V<8>FZBIsyAzUxLXWU=aU&er8U1>N%xZ-f20 z2~^_Y8}5Hri~c~}R(3vzhUr{meC5>b>{E|E-SO&0X%u7ee!?gobZhLgVD8CJf+`7q zK6{uh6mtQy%Rs-zQlmA_pnG5}`O)1*vKi(rrS+RVbkUQwbVi6!nrl$FmI>gL*o1i@ z9W?kFOOJLzp|u0vqtFX&HclOI2&(k<#3Y`XWKMSi)yHU;GiW)%$tAQ2(|4J8g&~83 z7I&kLe8|^s%D_ie`h*>8aL%NvOZ;Ayr|T0!pKq#;mD<58EU_XM4<9v+pCJUcW-XP4 zeobXd#WUkS2_nK820y#fdb42H*Z6OqU?ATg?;mv!-16{)wp#=U%QAL{p0&xjGbCJW zog9(PZ&nv&=5uXRYEVsnKG#Tk0A9H}F;f%)DmmEL1h*VCcK3zOU(NnbaPCdxzP_&1 z`$b2>@FHoXZR!!mlvBSYqxUF8lfO{$Exche3=lJ=4-036m`3PpRI|K zS-a#$sBW451k0bLklxr06>6rfT~WvPGR@grY3pfaRmv(irCxs+w;V?=7Qo<4Kt3iu zz?$#QyWwVRFf#mWWl-|N5WyY@Y{nCx5nleX_PxJxy`xeJH0Q0naJ&4c@KoGx@Kokh z-J<}LCcCHn#(nf{Dt5#3qIGxZ?V6lU00)ItIj1`YHs$2{U6e+{nd13W!&zg$>-X%K zD*ONgIF9qfzgcFzy3ZmQFHOYFcW5%2eb4E1@Uvi_aST5?C4YPFA~2lu9Dio3oI3^@ z4<48Rru-~nXDC`MQ`(&>tzIDC*VFND$TiXSpXchF*E*7dG#W=KV`n)AQ9w=RlBU=(F)fbgcn5CjCIO9$z_cO=q#Z%T`RNSCgZKm_SkdhZ}rkS3i-?;QbA zYUqgcl0cI0_&ncx$Nl-vlk&oJEcjg1;y!*n^Y*?i!UXHjr>{Oa=5 zFDCSSN)w>SF)jI3hqwq8ghuSnGa|2maQaLq;{5fTKDO(NTPm}J(>LVbVB)Q38aG=S zC!8*Yw~bCaHCgj@YxjN@NuurW0h3+01khrzHaY1($Wt{=AZ_{nWWNtF=AK<-|zxV@VN3vOSgcEV9aW zq9Q~`aIXF|wJ!Sh7d&x}dPy!aXe}7M$hn4etEaQZh1T{tDK4qsaJsJ2bCqt~WUOln zHfE!(08PFwp>h{ku%nTSN&;TiR6b5 z>qv6k+oUCUf^|Ml(P@|GLSh!}oh;d2YHo{e0=`7#ymqEk$dV~4R~l2`<)J5jmy2k* z!ePxiokyR;CCy_??xH}fk1qw$Xp=vvA$s6Nns^_zij%bmee&q4bp|6sK);FXdm<&a ziLBEkm4n|#hLSF>0jBNpJqiYGpu6mzM?5}f%8fr}3cb&Giel}Ceii7yuDe0g+>GC# zD^*G5+)z=+;8PTDH+0~-4ye$4b1mqbj`4f}V(TqWpp5 z1{}RP&(J1<$E2i0)MH?=tX+Q!7o(A+f*h&Qapw6LN{jT_E;3UiUURW=y-EH`ux}uk zNcPg+-rlCf#;zMOj8MAaX9EJVI-!Z9wGb;2;G~B`mr0`1d8*NSV)4;Zv{W7WWLf>s)K1xfXYD zQnTObW~plYfd)WtNm(362t z^lG&{6qAMzepF|eX>|v?!BD}h+a^_2-KnYRY!GqdCqTJQPB9C&H0uH1)mJ~dmVKQ$ zrMTId&5ERw)ch`Z1QwHAQ**sWM-=B^Q0P%gwSxt`Ku}c5g%7%eV-NnWX zjfiyqC2)5?0JA3$JA7TH`%sP9O3@4Qi#R6TGBVT_FZQ#yuQsY% zAEgBN9Ik6-5l6!G4DOQNp&d6+1pk$?gnoJ^s_>N#?W!BS9If)yv1M) z`9RZPW2>?RS+>4B*lEbGiea1f{rY$T2>g2e^_Q(UQDKro$)N2goZ{?&8s-d)4U`D| zhPRB^=1u2ZQqGE_yM5CzR@*vCcIKa-E(rcRoc6aUk})}WqhC7J0dG>;^=sZ-=$RuCaMkSQw8^E`H1`?+IV={@)r47 zB)Vxu+f#v~@O-4^75LFA7Z{5|Tk+LGq>F;pi^pP{DFR9HVceT_OZZ}pb&vJ5>RM)_ z9^?7Q@00yh_j`&a$a@YyC0M^0xik%^ih=@_M%#g(IbMveJa`D)@H`LV)s>dda{&JT z;Wq~`9gOCwfflwr{Dhhv|8MNmxwcjPj6HA$q?L`SXnox(iM* z)rwS`#*s61i2^d^i(u#%%cI`7C5R#(gU{pI{K{C~PwjJ7?|*ARzQmC9+<~Eem-F~$ zy&g>YkTY(Xy;mn27nzLX?>W!;t?JO?ZVRdCs}|Ud{OunVhmFx9Ko$N8|9;eq{L)!I z>Z0ijihbw<3?y)t3skd`x(-;$zTKc?-hr?Cirg&FIzWJ8&U2G65|1h1;S>c_ASe3J zxApbyQ42MFo5~~;WVq)7S9jij2hcay(P2*wV@a!f#+3^#nv5?PGp(~$3PS$T(#aMp z;*0<(jCqnxkwY=ECty_|cTTU{_KHVtU4?Ii9b)(}l1?bm!mYVkNq*)uFF5-#=@dvi$C6mW+&v^Sdf{aO%pt?i+Ljt9F=%UjtwDvP(=_xrk$jF}LsP^O}h z4fiu9%rAKFV0nbkua|Z6n<+KKOPG5yhc%5J$?Ep)KrYK_jvLLv%eqD@j?ktUE{G-% zkvj1OYa@wtP|fN6emC#^49T=CF|J8H0{mevm(ii4x7TNOgcH9@zTsx98T!O> z!dd)2Et}h&CGEPPvhGRG377UF7s=&_KR6H zQ~7HYK$!l_E9ijgq_g`YV8(IO>HWU0fMacE{fxJ^a8+t!XfRR7+Kcwlm|sktS`#`n zq>cd2`-jK4;yDQ@?(k@|setc#&n}^d($&Eu`ESSWYE!H_`x6+BGQ?^Ty}9f8j8?ai z)}!|x^2ps&hWdWN+dd-K@k^#0(l)IoK-aDz%12IOv&qH0AY;m1`1d^w1(+Pb)nbEhLZ9#1dNTU)33k(qcYa$eEJ^vI_)D?8Qpty zSwRRSy?n(RdrH+HxF1@AEU19G8h+DdVK@>*G6~1jzQ+g7pueejVv)gB#JS<`N|tnhkBM!IFfD=Y%RD6oV4^W@<#o2 ztjtgcRj^;BFMX>{k4Z2lQ0Tgztqk*vs6eR4L7})cw>Bx;8%;g|#Yz&5vA&0Mnq~0v zg%REwDuN_&8j7}WN9%_AFB(-lNw@2)T8Pbjj7xZKn5RH@Hr8;>o5!XqwRaVk_}eBY zMwJAB;HG=`x_k%;(;vAYO0_Yc-f}q;Hmh*mXO|QVg0&;B{KtoUAmV!VC+VEv8;RbV zHhk;H7|2|_*Y;dEwY7TC?NteXecP5n3k@9UQkQaA>mxvcu^$UAOk!1y3H<}Zg#37J0i+3l=_4 zev&S~p9#mgcu$(l_8F_R9=> z<_a)zB)dBF;a5l0bD(_{nc%b)r3^2QxG{)&B=udJn+w?mxkocu#HxIn_7B%w~LBb#~dDaQ)qQSO-g}g3FY8 ze2up`hOa@HuF&_6!mD?d1Z%es3)VS3mZ|RgO)oa^n5vj__?oCTFPZ%^Hma(uyRLdZ z=SXNC<~yw!GRj}|hl*#VzqoEVhYc|XP75#5H%iyS>`)ejx9cv1KPANnP0Ysa>C|i7 z5_GzMg2&SN_x^YrqWHkw-@MjCP8t=%I$7oKwGSz+SJ&@AL124k%uk1cnCGi<%|Jd{ z6Pu$TizEcRC1f+p7LBkulb$$-ehbKk3!ehjlvozBq)xG` zphz@5i&NnG_`gNni1b`cZQlxia`zP}Pe0WedC|IN*~bRGgFo;(VmbT;6b@%^Uj*W~ z*|9FOwaB@&-in~r=ZN0|-XkUY!8(?xA2f6(hybzkDOR=kVxS=Bqeb+Wg`$v*pE+W0 z6{&%&Sc|GuNZi%!1iC@tEc9HkkeI=L<@TWuyVsB0D00p1qxvy{yO&W{Eh{Yzrun4{6@N<5O1RJHdqA8YRpM2tukrY%LN;9WM33@!tjjuG;r$8lE z0I@V@;s0DHn!X2BqAuPCq7$?!wema8aHO3sTP@7vX^*Xo06SOAp}Qq?AY-o~wwz-V z=`u*RxHcW$f0=<&IYAp7oV!ZDk(2K!_(cDJ%(`4Z6;)#gyC-G7M!Y-s4pn*2l1~BO zL{&56-4><*(ovEOyGy%ADUT?^i8k~}u(S02$mqh5m_9U>!*ip=f;s8)yCEaw7u@L6 z#_3C*HJm2=E#j3{)LGU$#_w$BkC?q*g~Oj7ep0tSb#y}iJNTBtXWu?!dOHK05Knt; zTQbrjrBg_?68!6Ez77g?vI=udble)wIGKUa{HQX?S;f4PBO2Q0*IUP7lrIYnJR5J+ zBX>^_VLYnX;d%8N*5^c0*Lg-DA1e5n8cfekr=rQ5I=oMY2lq(;JXK9uZtzo1-Mhc< zVdZ9E|12s2OXb!W+;Y`BViB#^@L@ne|0x}p@o1eI#)C5E?O+e}MY~j2nBKyAVnydh z@%@uOjmO<0E217UB%D6&F9oCC4Si;k=XM~HP+N&;TYmTJ$Z<|bl4bBD%b~!8D^q`B zbQ`;4Q-`f{t`CoWJ~=hPEd;w@sn53)5sO_AD*Em>U0C(&3Y}jQrPg5~*3urm*b+4B zruh2jHwU(^GPMLZj$m_he9n7EoNg1DBRO`d)p7{LDPi+rFanvFxU}*DEo@c z-%D-Z^t(qr9zE%b=_ALq`dsC{l&W;ha~*V9`XuS?p+0ns->i7BAh@O)x}Z~|m#RO< z5nonD%BuFt$XG8MP-129y)d{~-V>fLR8T^qZ~d*w+@lD$W7QOLz`=+IRwqLs&6VY5 z{69`baDxyT)ignNbLNZeesWCBTjYgP?^>~n)>^{1(wA4P8P-+kZS?Km68HL~c)fdV zN8e;Cz@RHZs?=C};}4)4hCjFVH__p-R8d{$(LAolM&-er<%VBgfr=Ou!Ip~IntR`V zxo6A+0zkCu!z!OPWQN+o=2barreD0|@hFxJHFs(BOMLw{nIzALOA@D7rCwI+t^`Oi zRExNUrt||@o2SiYpFP9)%oUdfZlQm%-5b^?VmQ?7OMOfy|HuVf2JUWm5RZHwA)xf* z!`BrvcUEHi>G$#T=rkvhQwLv%Nwo+!I#~XY=-_cDq!dAsIT%l?DZS`uh5w_*#Auj& zbs+N75ZGcf`!XJEVaG!>YC_HV(<8Q1ft!zH`}U0$xj!O-)W8$*%e8~Q3whue;Qia8 z;cvLtx52>$5Nh9xdvhW-VoWay)_vNNK~%%Esa&pS&$cZ-t2bY}04HWt#fa-t{}!@Kun)&El&Jpfu8x`hQtzu4Q;c~NXeMF) zD5o!ChFijKv!Jl-fqF;S2ZZ-3tjwt?bfJsP;_0Ya>S;eL3N+teX*CbRQ*HvCGVBN& zDv`b_oxIb8U?$xyL z7hnb8sQ$a|Xv&4uB|=mLU14|c*Gn%y1rHaNgB7P^G4pkiV>8l|E451`ZS2Puk>W^;6=X zaYrTV0?5kO-rKTx`970cNJ+yB)x39xLeu;faT%`>+$Y14TSN>$5p{7c-)>?!yJ!E*L^2gE4t0lA+rOEcYYmO9+mP8=vfB^NRqV;BuIgYzu%wpVId^lBX7a9NwhXMAg3-!QDLn+XaMc0z~Y=Wa`L*;a*&Qp zQ@Fe2laW&=!@Xa$Ck2~p6*kzZHQ??9P??DHTUaZ`;HTU2HybkijT=cnbTiF~!>I#8 z=8dcJhH#@@%BKYI<9gfA)G`S(4vS9Q-VidUFH9BFnktHrY$CJaLDD8!p!9Zbgm=9} zRCb%UubtkJd9%lWsV}FRMH-qwa%3>O_LqBPylCB~b6Lt>V_t`i-Qy`NJkA;9#LKLl z-c#?1*SX*NOM&oXuFBU_Le@MWRp)g;m0>opFL$&7mg$AfdzNdUmTnLfV$IjqUbbdy z@LRIm==$mFuIptB_I3g9H{+R$`@EKR1^BMGZQiqvzG7fx;uUcYZ^1J zmjF8dwu1Iv*kc$wB!%GEYf!@3PO{Jj-JA#VQ)9dvLq=(_6BZXwV@H z|EOL~?=4W@H6wj9W;naGHX$(i>PG+Rw9K`n2}9$b-|))rnKtVI6pxR$r1Y$JIV zNhEpb5nAAy>4Nlj-~%cdz0LY4BR}7W*W~d zcm;Vf3@A-nZC~0@)vgC*=MPS~_dr3qQ}AmlN_&PM!EK5!rxt5}5LxDIR!I}8P9QdK zeo4iDJP(KK2{%(Uo>>vc--+C)A$A0xqN^$mF#R3UzEM{W-SWtSc{5rLXWBDdJ%JX3wAd8%;kw9we#}mzZ|mEvVZ3(+-4^r^g+V zObZEZBI(vZOA>`7&eZJal5^G>l(mKx?7CGdO&`avLr)tSQC_yV*DvW9%O*Yp~gj>_M?&L$F@=2Y)iNM8Ch&Q+eha z0eV*^z~&-WBZsU0O8-9ND5Wr~XZv9I_~%#|PRhfRkuqki%3$rdv!lzCjNY{gg3H6e zPS|Ia)7|~XpsYEn) z8Yp(G1csp5eGh&|15AqBPy~o7M<17jG0y1W&bo5qv9f$37$Kigt@W)>6^DpV8#K%q zIPP9Aj1O7T?&l1VdN7NA-a6tSFV9dep*yfaBwV!Pl+7K7+C7B*@l*(ui6kC-iiF|o z>aS0Vp#Gs3epD}d!2C*)$3t7YYz&~o!H`y-NN8(bA_$rJw(Cfu9iAY&2cVTc&*{oC zhTiph~A z`cy1BdzZo`>}#F=gwc&>24USlDhxiV!menW-F{JBEoF6QiKx`J2Aqv>67H-~4cy(G z5BnLX}+@Tod{ZQq^9s5z{KA|fP8QG_u zOX$73-|$3`Zh3*HjIQQJyc|Bi6%yOZd!kuAW12|PS`T}QmaC7d=LrVT^5>%qyomd@ zdG@nc`UIDcum~NF21|x)q|4@Zi@5AOay@abDsFWW6AhenwM-jYWb+LCDr{!mLVPFM zYurt&H^}jllct<0=$e6mELW^Q_#tg6=9A16 zJq58=gt-)mJXv>Mr|y7wIvxS=<!k>KF78F<9LSo zQ27V(*afA=ZBGk6Q{Xb>%AICL&Lud*FJz>BH3$fYrn#RHW!cA6Nl@lmCpgm} z2CfH6(sAgNmkXOHEPoZW7on3)<$AYqyOv`&RK$rd-an!fPj`rGlrK)Yjr?rIF94Q( zfKMld@xMLycFi*$F_1qwS&FJszBA#z7cr;R%sk1gjG7ZC(3hHe`qj}qc@-0Wb?mFH zaboPTz3-85HMM^4Q4i|@xCvB&J^$P-HtjwOvS1II+xUYEB10Kc)ql-|La}1d)eoat zHUKPxePvQ4;5v&WM_RXe{Scb;@Vz<)?c}8nCVdb)0MEm2&>3iycsHh3GEBEun%Yx% za?0_UNOB^Y6AAv2)rvl8A64xE02PSi5qdF#0(0v>$B8qTfbSHgd(a1uUpjBu+%U8H z@1^mY5y2lRHo?XYqNj;vvk-otmk{bm@V5;l7W2!x=j94M-9dvXCwXhBI_P_0i z`vK@SG%oE$WMkP(dH#-3Q1093qc_*9L(SHxt$D0`5Pr{HUEdF@PNyiP>W1|7>HK)6 zDG17Gow4rOZ->N{ zEjkj1>fjfw8@x|pWVL^7xYst|^6)8^aJ_9U7RGjb7Jk`-y^?((0n|G}qBr`6Eoo|8 z+ZM#N6)zY68lqfcKd13QGWp!omm(2>uA{k&V@B$tTsmH$->>PMqnZ%l0?Z5uB|Z8S zo*{qO7x7XE{l_zC|3)YH%d)sfuR1o(*|0&0;=KpWK=eC8qEFnObJLifg<#v0)n+vS zD@Ov=@-$wnLMuaHzVkt?tzu@ZjtAltbjnsstSCt$zx7HVFtL{;0fRXo0caatl}RJj zvk%qRH)3Vw`2jINv=8kyR8Ro@i!^s-lXf3|W!~j1F5ah_(w8XpR5Vy+EZ!db6pBHe ze!cF#z^i}HX#Yg>HQG!DWUx?W(sScr=S_K=uesFuzV`!~*y^H`9%1!uPKOP^W%c|} zXQGaS(&!q}h5MthPT%C%z>^76y3w>|aFJ?4wJbWqF)gaLm0cODX}}d zqD-`Ro?9>cA}XmpN%JNb!*sg&(5WK$BeGpXjfdPaX;F0medJDIdeVk=e-*p*$n(*bBNYrRn?rKvTzM2l1;N}c6lP50^ND( zFf8s!w1J%;h(>q~O_>EVM7od*rt+RxdW%rXF#TqdMO8b^aqI!Fz5(lOL#(GJwL8V; z#EWZ?M_sy>XhjPaGh!hJzQ?{-o4dJv0W%sRZVfO3GK24PYe;fuKQ)w}`CHlYuDZ3X z#E&O}j-iT(xa0(4w=_HGz)nGUjr*PD)R87h>;#Bh-9DA+^!@1L%c);DtWOnEmBjmF zVt&GYwBgFRG=ZLzq7spQ2CRkyKdqh%0@3Ziq9-lMkIv#!rA#<{@gt@p zo8sEo{^71bHB7~-(^1ADrTWc;+kh+7gLB0(!aOCht=PD>r75XQFY+O`I!KtiW_8x=Chdz_NJ5j1s3?~pH#$02h zck_93G+faD(MuC`N*c^kOIc~&42a-H%X45(lzPnS@5eJ-;}Xfg7t(#WkvP}>htH}Z z3~4tvRUA$jUN`m*(i@qejz~)YWv_aTO*92uI-586;aj~!Y|G))H-k+vp$m&8m`H}t zoU4!MrJ^{rGnwty(U`BUG^jUg@=c5Kfpa?0?xc>mDHngyk`ye3h;4@|%a;0wTe4phfM+WoH8x8%S0sYzhgqKAJSr4*eK?d zmm>X$JqJT9{1T&Bd>s^DUG=X%H}`H^<2QnBmukL`PH_SkFv|Lk5WP{`=XD)d=F%&J zgP7eMw6>jUBI)fR=+(U&6Fp_2!KowRFU@0Th^<70&S>0y(J6YacOw)W;DFfn{*%5p z>F%DC)3Jix&pS4&x&ZFJb~bE1k$PRGu&Ub^zN?w!aQB#Ebdgbm{A|4%l+ zs)y5XDi~hyWV)^ZiEp|8gEA*q>Lk$k~myMH^Fxwg6AK>ZOMF-I^t#O+4RH|HPKaGy54{W#Id zQUJHmfhLU~c#{FL_9@y*T(9OBO9IgxjsjKb9Eldv23ph4&b0ik0EwkOVk3w@_6m{Y z>O*MPLLF$Xe5$Y;g$!-Mp{rSjYjz@MV_2w0p$s{$wVh`?SQy>h_PLsuq71Ag#!n0p zW*=(b*I9d+z?Yk=?d8UAe)Qvukse}4PP7QYwyM}QkD$^Eg=&m}6t4ITUolwwcX(U= zcWp6&NzQyC65OBM;XLl>EF(l4dzjbBq9!Bh+`1a=!a^GC4&ji_RbcS=LW(r(pDX2{ zTSGa6?wnfwbQ#GKi;fhILN5j`wxa7_GWTDC*Yr1U)gTzdz{icla+|ki5KPQ5c!v{y z>(KsY@LF#nX1&YM>n{sIiFw}?fRI|JSOXQ2(i>XD95cm4eoyxAzb3h4B2K*laHH2G zM&awX)mhqix1yh-pwRb%e=tSIOou11c&!usvGND<+vEv@#Ss6QPRe#Ex{V!17rs$y zoQ1LvH>rAC^~;~`WGp(!EQ8>uS2{@L7*K71#y{T|G9`aovpb@p^=`G2ZZ%6YnMEnW zG0i3R+ym<-sb#zrfY6TsH97L{eu`vP(@2&b=}+nf+qX zQfeEn847~O@_sZlSO^vSN~~m_@uw9I5Y}6bznHV{cTPxTW+aRNc*ocCBcTrn!*Dsf z)U!sel{``fyKjo889Z;2@-xWi%55MzIT{QSZ`nJJzo~^9NqfO>wf$n6`BA@FjvTgm zOvrMjQhXfOSo}|lnhH2BDgb6G^5Sob2lVBlhpp}`9Rd)5ADc=Z&{A+twv2mN{+mq} z8}@dkopa^g-}G0k*%xg9k$w1>Z|Q1phV>

_geH^TkpP@}dC^;gNU0-WD`D|I|F= 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%0A4Wx4n{Zs5kJE{WCq!@?;2Q`9*9Z@ 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 J*BXi?1_RegBP1C6H9nY`0Gf8Hy*Uzv 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

i@o*f8nK#r9$gF{w;Y%2`;T38XIfYX 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

U(9lV8Ky*#WqUgycMl6$J}T?R%bD?lcv4m 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

EMsd*_yhJ&FCP%4w!*nB~6-Y|5+5wIMlIW@<#` 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

7rk+QsSa41Id|4Ci?fMzo_2l{0m^V-rM!- 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Μ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

@HK1o&E4I7_#Wk&q|e)DvYbk1!wY~OXK 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%0At2KFNl9uYuz6$5b8G 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 z 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

+lYP0De=)3_;KwUH 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 z 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

9PnbDK5O05sJX+-SPz4Sy|s1ZJmpp3I}M3%hiN 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 literal 0 HcmV?d00001 diff --git a/internal/otel_collector/testbed/README.md b/internal/otel_collector/testbed/README.md new file mode 100644 index 00000000000..93a8dc5d2d6 --- /dev/null +++ b/internal/otel_collector/testbed/README.md @@ -0,0 +1,136 @@ +# OpenTelemetry Collector Testbed + +Testbed is a controlled environment and tools for conducting end-to-end tests for the Otel Collector, +including reproducible short-term benchmarks, correctness tests, long-running stability tests and +maximum load stress tests. + +## Usage + +For each type of tests that should have a summary report create a new directory and then a test suite function which utilizes `*testing.M`. This function should delegate all functionality to `testbed.DoTestMain` supplying a global instance of `testbed.TestResultsSummary` to it. + +Each test case within the suite should create a `testbed.TestCase` and supply implementations of each of the various interfaces the `NewTestCase` function takes as parameters. + +## DataFlow + +`testbed.TestCase` uses `LoadGenerator` and `MockBackend` to further encapsulate pluggable components. `LoadGenerator` further encapsulates `DataProvider` and `DataSender` in order to generate and send data. `MockBackend` further encapsulate `DataReceiver` and provide consume functionality. + +For instance, if using the existing end-to-end test, the general dataflow can be (Note that MockBackend does not really have a consumer instance, only to make it intuitive, this diagram draws it a separate module): + +![e2e diagram](./e2e_diagram.jpeg) + +## Pluggable Test Components + +* `DataProvider` - Generates test data to send to receiver under test. + * `PerfTestDataProvider` - Implementation of the `DataProvider` for use in performance tests. Tracing IDs are based on the incremented batch and data items counters. + * `GoldenDataProvider` - Implementation of `DataProvider` for use in correctness tests. Provides data from the "Golden" dataset generated using pairwise combinatorial testing techniques. +* `DataSender` - Sends data to the collector instance under test. + * `JaegerGRPCDataSender` - Implementation of `DataSender` which sends to `jaeger` receiver. + * `OCTraceDataSender` - Implementation of `DataSender` which sends to `opencensus` receiver. + * `OCMetricsDataSender` - Implementation of `DataSender` which sends to `opencensus` receiver. + * `OTLPTraceDataSender` - Implementation of `DataSender` which sends to `otlp` receiver. + * `OTLPMetricsDataSender` - Implementation of `DataSender` which sends to `otlp` receiver. + * `ZipkinDataSender` - Implementation of `DataSender` which sends to `zipkin` receiver. +* `DataReceiver` - Receives data from the collector instance under test and stores it for use in test assertions. + * `OCDataReceiver` - Implementation of `DataReceiver` which receives data from `opencensus` exporter. + * `JaegerDataReceiver` - Implementation of `DataReceiver` which receives data from `jaeger` exporter. + * `OTLPDataReceiver` - Implementation of `DataReceiver` which receives data from `otlp` exporter. + * `ZipkinDataReceiver` - Implementation of `DataReceiver` which receives data from `zipkin` exporter. +* `OtelcolRunner` - Configures, starts and stops one or more instances of otelcol which will be the subject of testing being executed. + * `ChildProcess` - Implementation of `OtelcolRunner` runs a single otelcol as a child process on the same machine as the test executor. + * `InProcessCollector` - Implementation of `OtelcolRunner` runs a single otelcol as a go routine within the same process as the test executor. +* `TestCaseValidator` - Validates and reports on test results. + * `PerfTestValidator` - Implementation of `TestCaseValidator` for test suites using `PerformanceResults` for summarizing results. + * `CorrectnessTestValidator` - Implementation of `TestCaseValidator` for test suites using `CorrectnessResults` for summarizing results. +* `TestResultsSummary` - Records itemized test case results plus a summary of one category of testing. + * `PerformanceResults` - Implementation of `TestResultsSummary` with fields suitable for reporting performance test results. + * `CorrectnessResults` - Implementation of `TestResultsSummary` with fields suitable for reporting data translation correctness test results. + +## Adding New Receiver and/or Exporters to the testbed + +Generally, when designing a test for new exporter and receiver components, developers should mainly focus on designing and implementing the components with yellow background in the diagram above as the other components are implemented by the testbed framework: + +* `DataSender` - This part should provide below interfaces for testing purpose: + + * `Start()` - Start sender and connect to the configured endpoint. Must be called before sending data. + * `Flush()` - Send any accumulated data. + * `GetCollectorPort()` - Return the port to which this sender will send data. + * `GenConfigYAMLStr()` - Generate a config string to place in receiver part of collector config so that it can receive data from this sender. + * `ProtocolName()` - Return protocol name to use in collector config pipeline. + +* `DataReceiver` - This part should provide below interfaces for testing purpose: + + * `Start()` - Start receiver. + * `Stop()` - Stop receiver. + * `GenConfigYAMLStr()` - Generate a config string to place in exporter part of collector config so that it can send data to this receiver. + * `ProtocolName()` - Return protocol name to use in collector config pipeline. + +* `Testing` - This part may vary from what kind of testing developers would like to do. In existing implementation, we can refer to [End-to-End testing](https://github.com/EdZou/opentelemetry-collector/blob/master/testbed/tests/e2e_test.go), [Metrics testing](https://github.com/EdZou/opentelemetry-collector/blob/master/testbed/tests/metric_test.go), [Traces testing](https://github.com/EdZou/opentelemetry-collector/blob/master/testbed/tests/trace_test.go) and [Correctness testing](https://github.com/EdZou/opentelemetry-collector/blob/master/testbed/correctness/correctness_test.go). For instance, if developers would like to design a trace test for a new exporter and receiver: + + * ```go + func TestTrace10kSPS(t *testing.T) { + tests := []struct { + name string + sender testbed.DataSender + receiver testbed.DataReceiver + resourceSpec testbed.ResourceSpec + }{ + { + "NewExporterOrReceiver", + testbed.NewXXXDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewXXXDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: XX, + ExpectedMaxRAM: XX, + }, + }, + ... + } + + processors := map[string]string{ + "batch": ` + batch: + `, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + Scenario10kItemsPerSecond( + t, + test.sender, + test.receiver, + test.resourceSpec, + performanceResultsSummary, + processors, + ) + }) + } + } + ``` + +## Run Tests and Get Results + +Here providing some examples of how to run and get the results of testing. + +1. Under the [collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib) repo, by running following command: + +``` + cd /testbed/tests + TESTBED_CONFIG=local.yaml go test -v +``` + +​ Then get the result: + +![collector-contrib tests result](./CCRepo_result.png) + +2. Under [Collector/testbed/](https://github.com/EdZou/opentelemetry-collector/tree/master/testbed) repo, here taking correctness tests as an example, by running: + +``` + cd correctness + source ~/.bash_profile # remember you should enable your envir var here + ./runtests.sh +``` + +Then get the result: + +![collector correctness tests result](./correctness_result.png) + diff --git a/internal/otel_collector/testbed/correctness/.gitignore b/internal/otel_collector/testbed/correctness/.gitignore new file mode 100644 index 00000000000..0482cb4e736 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/.gitignore @@ -0,0 +1,2 @@ +results + diff --git a/internal/otel_collector/testbed/correctness/metrics/correctness_test_case.go b/internal/otel_collector/testbed/correctness/metrics/correctness_test_case.go new file mode 100644 index 00000000000..c8f44b06ebb --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/correctness_test_case.go @@ -0,0 +1,111 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "log" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/service/defaultcomponents" + "go.opentelemetry.io/collector/testbed/correctness" + "go.opentelemetry.io/collector/testbed/testbed" +) + +type correctnessTestCase struct { + t *testing.T + sender testbed.DataSender + receiver testbed.DataReceiver + harness *testHarness + collector *testbed.InProcessCollector +} + +func newCorrectnessTestCase( + t *testing.T, + sender testbed.DataSender, + receiver testbed.DataReceiver, + harness *testHarness, +) *correctnessTestCase { + return &correctnessTestCase{t: t, sender: sender, receiver: receiver, harness: harness} +} + +func (tc *correctnessTestCase) startCollector() { + tc.collector = testbed.NewInProcessCollector(componentFactories(tc.t)) + _, err := tc.collector.PrepareConfig(correctness.CreateConfigYaml(tc.sender, tc.receiver, nil, "metrics")) + require.NoError(tc.t, err) + rd, err := newResultsDir(tc.t.Name()) + require.NoError(tc.t, err) + err = rd.mkDir() + require.NoError(tc.t, err) + fname, err := rd.fullPath("agent.log") + require.NoError(tc.t, err) + log.Println("starting collector") + err = tc.collector.Start(testbed.StartParams{ + Name: "Agent", + LogFilePath: fname, + CmdArgs: []string{"--metrics-level=NONE"}, + }) + require.NoError(tc.t, err) +} + +func (tc *correctnessTestCase) stopCollector() { + _, err := tc.collector.Stop() + require.NoError(tc.t, err) +} + +func (tc *correctnessTestCase) startTestbedSender() { + log.Println("starting testbed sender") + err := tc.sender.Start() + require.NoError(tc.t, err) +} + +func (tc *correctnessTestCase) startTestbedReceiver() { + log.Println("starting testbed receiver") + err := tc.receiver.Start(&testbed.MockTraceConsumer{}, tc.harness, &testbed.MockLogConsumer{}) + require.NoError(tc.t, err) +} + +func (tc *correctnessTestCase) stopTestbedReceiver() { + log.Println("stopping testbed receiver") + err := tc.receiver.Stop() + require.NoError(tc.t, err) +} + +func (tc *correctnessTestCase) sendFirstMetric() { + tc.harness.sendNextMetric() +} + +func (tc *correctnessTestCase) waitForAllMetrics() { + log.Println("waiting for allMetricsReceived") + for { + select { + case <-time.After(10 * time.Second): + tc.t.Fatal("Deadline exceeded while waiting to receive metrics") + return + case <-tc.harness.allMetricsReceived: + log.Println("all metrics received") + return + } + } +} + +func componentFactories(t *testing.T) component.Factories { + factories, err := defaultcomponents.Components() + require.NoError(t, err) + return factories +} diff --git a/internal/otel_collector/testbed/correctness/metrics/doc.go b/internal/otel_collector/testbed/correctness/metrics/doc.go new file mode 100644 index 00000000000..f39c88252de --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/doc.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package metrics contains functionality for testing an otelcol pipeline end to end for metric correctness. +// Partly because of how Prometheus works (being pull-based) metrics correctness works differently than +// the performance testbed in the parent directory. Whereas performance testing sends a relatively large +// number of datapoints into the collector, this package sends metrics in one at a time, and only sends the +// next datapoint when the previous datapoint has been processed and compared to the original. +// +// Mostly simlar to the performance testing pipeline, this pipeline looks like the following: + +// [testbed exporter] -> [otelcol receiver] -> [otelcol exporter] -> [testbed receiver] -> [test harness] +// +// the difference being the testHarness, which is connected to [testbed receiver] as its metrics +// consumer, listening for datapoints. To start the process, one datapoint is sent into the testbed +// exporter, it goes through the pipeline, and arrives at the testbed receiver, which passes it along to the +// test harness. The test harness compares the received datapoint to the original datapoint it sent, and saves +// any diffs it found in a diffAccumulator instance. Then it sends the next datapoint. This continues until +// there are no more datapoints. The simple diagram above should have a loop, where [test harness] connects +// back to [testbed exporter]. +// +// Datapoints are supplied to the testHarness by a metricSupplier, which receives all of the metrics it needs +// upfront. Those metrics are in turn generated by a metricGenerator, which receives its config from a PICT +// generated file, as the trace correctness funcionality does. +package metrics diff --git a/internal/otel_collector/testbed/correctness/metrics/metric_diff.go b/internal/otel_collector/testbed/correctness/metrics/metric_diff.go new file mode 100644 index 00000000000..edb2a33a644 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/metric_diff.go @@ -0,0 +1,332 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "fmt" + "reflect" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// MetricDiff is intended to support producing human-readable diffs between two MetricData structs during +// testing. Two MetricDatas, when compared, could produce a list of MetricDiffs containing all of their +// differences, which could be used to correct the differences between the expected and actual values. +type MetricDiff struct { + ExpectedValue interface{} + ActualValue interface{} + Msg string +} + +func (mf MetricDiff) String() string { + return fmt.Sprintf("{msg='%v' expected=[%v] actual=[%v]}\n", mf.Msg, mf.ExpectedValue, mf.ActualValue) +} + +func pdmToPDRM(pdm []pdata.Metrics) (out []pdata.ResourceMetrics) { + for _, md := range pdm { + rms := md.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + rm := rms.At(i) + out = append(out, rm) + } + } + return out +} + +func diffRMSlices(sent []pdata.ResourceMetrics, recd []pdata.ResourceMetrics) []*MetricDiff { + var diffs []*MetricDiff + if len(sent) != len(recd) { + return []*MetricDiff{{ + ExpectedValue: len(sent), + ActualValue: len(recd), + Msg: "Sent vs received ResourceMetrics not equal length", + }} + } + for i := 0; i < len(sent); i++ { + sentRM := sent[i] + recdRM := recd[i] + diffs = diffRMs(diffs, sentRM, recdRM) + } + return diffs +} + +func diffRMs(diffs []*MetricDiff, expected pdata.ResourceMetrics, actual pdata.ResourceMetrics) []*MetricDiff { + diffs = diffResource(diffs, expected.Resource(), actual.Resource()) + diffs = diffILMSlice( + diffs, + expected.InstrumentationLibraryMetrics(), + actual.InstrumentationLibraryMetrics(), + ) + return diffs +} + +func diffILMSlice( + diffs []*MetricDiff, + expected pdata.InstrumentationLibraryMetricsSlice, + actual pdata.InstrumentationLibraryMetricsSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, actual.Len(), expected.Len(), "InstrumentationLibraryMetricsSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diffILM(diffs, expected.At(i), actual.At(i)) + } + return diffs +} + +func diffILM( + diffs []*MetricDiff, + expected pdata.InstrumentationLibraryMetrics, + actual pdata.InstrumentationLibraryMetrics, +) []*MetricDiff { + return diffMetrics(diffs, expected.Metrics(), actual.Metrics()) +} + +func diffMetrics(diffs []*MetricDiff, expected pdata.MetricSlice, actual pdata.MetricSlice) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, actual.Len(), expected.Len(), "MetricSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = DiffMetric(diffs, expected.At(i), actual.At(i)) + } + return diffs +} + +func DiffMetric(diffs []*MetricDiff, expected pdata.Metric, actual pdata.Metric) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffMetricDescriptor(diffs, expected, actual) + if mismatch { + return diffs + } + switch actual.DataType() { + case pdata.MetricDataTypeIntGauge: + diffs = diffIntPts(diffs, expected.IntGauge().DataPoints(), actual.IntGauge().DataPoints()) + case pdata.MetricDataTypeDoubleGauge: + diffs = diffDoublePts(diffs, expected.DoubleGauge().DataPoints(), actual.DoubleGauge().DataPoints()) + case pdata.MetricDataTypeIntSum: + diffs = diff(diffs, expected.IntSum().IsMonotonic(), actual.IntSum().IsMonotonic(), "IntSum IsMonotonic") + diffs = diff(diffs, expected.IntSum().AggregationTemporality(), actual.IntSum().AggregationTemporality(), "IntSum AggregationTemporality") + diffs = diffIntPts(diffs, expected.IntSum().DataPoints(), actual.IntSum().DataPoints()) + case pdata.MetricDataTypeDoubleSum: + diffs = diff(diffs, expected.DoubleSum().IsMonotonic(), actual.DoubleSum().IsMonotonic(), "DoubleSum IsMonotonic") + diffs = diff(diffs, expected.DoubleSum().AggregationTemporality(), actual.DoubleSum().AggregationTemporality(), "DoubleSum AggregationTemporality") + diffs = diffDoublePts(diffs, expected.DoubleSum().DataPoints(), actual.DoubleSum().DataPoints()) + case pdata.MetricDataTypeIntHistogram: + diffs = diff(diffs, expected.IntHistogram().AggregationTemporality(), actual.IntHistogram().AggregationTemporality(), "IntHistogram AggregationTemporality") + diffs = diffIntHistogramPts(diffs, expected.IntHistogram().DataPoints(), actual.IntHistogram().DataPoints()) + case pdata.MetricDataTypeDoubleHistogram: + diffs = diff(diffs, expected.DoubleHistogram().AggregationTemporality(), actual.DoubleHistogram().AggregationTemporality(), "DoubleHistogram AggregationTemporality") + diffs = diffDoubleHistogramPts(diffs, expected.DoubleHistogram().DataPoints(), actual.DoubleHistogram().DataPoints()) + } + return diffs +} + +func diffMetricDescriptor( + diffs []*MetricDiff, + expected pdata.Metric, + actual pdata.Metric, +) ([]*MetricDiff, bool) { + diffs = diff(diffs, expected.Name(), actual.Name(), "Metric Name") + diffs = diff(diffs, expected.Description(), actual.Description(), "Metric Description") + diffs = diff(diffs, expected.Unit(), actual.Unit(), "Metric Unit") + return diffValues(diffs, expected.DataType(), actual.DataType(), "Metric Type") +} + +func diffDoublePts( + diffs []*MetricDiff, + expected pdata.DoubleDataPointSlice, + actual pdata.DoubleDataPointSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, expected.Len(), actual.Len(), "DoubleDataPointSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diffDoublePt(diffs, expected.At(i), actual.At(i)) + } + return diffs +} + +func diffDoublePt( + diffs []*MetricDiff, + expected pdata.DoubleDataPoint, + actual pdata.DoubleDataPoint, +) []*MetricDiff { + diffs = diff(diffs, expected.Value(), actual.Value(), "DoubleDataPoint value") + return diffDoubleExemplars(diffs, expected.Exemplars(), actual.Exemplars()) +} + +func diffDoubleHistogramPts( + diffs []*MetricDiff, + expected pdata.DoubleHistogramDataPointSlice, + actual pdata.DoubleHistogramDataPointSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, expected.Len(), actual.Len(), "HistogramDataPointSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diffDoubleHistogramPt(diffs, expected.At(i), actual.At(i)) + } + return diffs +} + +func diffDoubleHistogramPt( + diffs []*MetricDiff, + expected pdata.DoubleHistogramDataPoint, + actual pdata.DoubleHistogramDataPoint, +) []*MetricDiff { + diffs = diff(diffs, expected.Count(), actual.Count(), "DoubleHistogramDataPoint Count") + diffs = diff(diffs, expected.Sum(), actual.Sum(), "DoubleHistogramDataPoint Sum") + diffs = diff(diffs, expected.BucketCounts(), actual.BucketCounts(), "DoubleHistogramDataPoint BucketCounts") + diffs = diff(diffs, expected.ExplicitBounds(), actual.ExplicitBounds(), "DoubleHistogramDataPoint ExplicitBounds") + // todo LabelsMap() + return diffDoubleExemplars(diffs, expected.Exemplars(), actual.Exemplars()) +} + +func diffDoubleExemplars( + diffs []*MetricDiff, + expected pdata.DoubleExemplarSlice, + actual pdata.DoubleExemplarSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, expected.Len(), actual.Len(), "DoubleExemplarSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diff(diffs, expected.At(i).Value(), actual.At(i).Value(), "DoubleExemplar Value") + } + return diffs +} + +func diffIntHistogramPts( + diffs []*MetricDiff, + expected pdata.IntHistogramDataPointSlice, + actual pdata.IntHistogramDataPointSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, expected.Len(), actual.Len(), "HistogramDataPointSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diffIntHistogramPt(diffs, expected.At(i), actual.At(i)) + } + return diffs +} + +func diffIntHistogramPt( + diffs []*MetricDiff, + expected pdata.IntHistogramDataPoint, + actual pdata.IntHistogramDataPoint, +) []*MetricDiff { + diffs = diff(diffs, expected.Count(), actual.Count(), "DoubleHistogramDataPoint Count") + diffs = diff(diffs, expected.Sum(), actual.Sum(), "DoubleHistogramDataPoint Sum") + diffs = diff(diffs, expected.BucketCounts(), actual.BucketCounts(), "DoubleHistogramDataPoint BucketCounts") + diffs = diff(diffs, expected.ExplicitBounds(), actual.ExplicitBounds(), "DoubleHistogramDataPoint ExplicitBounds") + // todo LabelsMap() + return diffIntExemplars(diffs, expected.Exemplars(), actual.Exemplars()) +} + +func diffIntExemplars( + diffs []*MetricDiff, + expected pdata.IntExemplarSlice, + actual pdata.IntExemplarSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, expected.Len(), actual.Len(), "DoubleExemplarSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diff(diffs, expected.At(i).Value(), actual.At(i).Value(), "DoubleExemplar Value") + } + return diffs +} + +func diffIntPts( + diffs []*MetricDiff, + expected pdata.IntDataPointSlice, + actual pdata.IntDataPointSlice, +) []*MetricDiff { + var mismatch bool + diffs, mismatch = diffValues(diffs, expected.Len(), actual.Len(), "IntDataPointSlice len") + if mismatch { + return diffs + } + for i := 0; i < expected.Len(); i++ { + diffs = diffIntPt(diffs, expected.At(i), actual.At(i)) + } + return diffs +} + +func diffIntPt( + diffs []*MetricDiff, + expected pdata.IntDataPoint, + actual pdata.IntDataPoint, +) []*MetricDiff { + return diff(diffs, expected.Value(), actual.Value(), "IntDataPoint value") +} + +func diffResource(diffs []*MetricDiff, expected pdata.Resource, actual pdata.Resource) []*MetricDiff { + return diffAttrs(diffs, expected.Attributes(), actual.Attributes()) +} + +func diffAttrs(diffs []*MetricDiff, expected pdata.AttributeMap, actual pdata.AttributeMap) []*MetricDiff { + if !reflect.DeepEqual(expected, actual) { + diffs = append(diffs, &MetricDiff{ + ExpectedValue: attrMapToString(expected), + ActualValue: attrMapToString(actual), + Msg: "Resource attributes", + }) + } + return diffs +} + +func diff(diffs []*MetricDiff, expected interface{}, actual interface{}, msg string) []*MetricDiff { + out, _ := diffValues(diffs, expected, actual, msg) + return out +} + +func diffValues( + diffs []*MetricDiff, + expected interface{}, + actual interface{}, + msg string, +) ([]*MetricDiff, bool) { + if !reflect.DeepEqual(expected, actual) { + return append(diffs, &MetricDiff{ + Msg: msg, + ExpectedValue: expected, + ActualValue: actual, + }), true + } + return diffs, false +} + +func attrMapToString(m pdata.AttributeMap) string { + out := "" + m.ForEach(func(k string, v pdata.AttributeValue) { + out += "[" + k + "=" + v.StringVal() + "]" + }) + return out +} diff --git a/internal/otel_collector/testbed/correctness/metrics/metric_diff_test.go b/internal/otel_collector/testbed/correctness/metrics/metric_diff_test.go new file mode 100644 index 00000000000..4f87e2f7808 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/metric_diff_test.go @@ -0,0 +1,103 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/goldendataset" +) + +func TestSameMetrics(t *testing.T) { + expected := goldendataset.DefaultMetricData() + actual := goldendataset.DefaultMetricData() + diffs := diffMetricData(expected, actual) + assert.Nil(t, diffs) +} + +func diffMetricData(expected pdata.Metrics, actual pdata.Metrics) []*MetricDiff { + expectedRMSlice := expected.ResourceMetrics() + actualRMSlice := actual.ResourceMetrics() + return diffRMSlices(toSlice(expectedRMSlice), toSlice(actualRMSlice)) +} + +func toSlice(s pdata.ResourceMetricsSlice) (out []pdata.ResourceMetrics) { + for i := 0; i < s.Len(); i++ { + out = append(out, s.At(i)) + } + return out +} + +func TestDifferentValues(t *testing.T) { + expected := goldendataset.DefaultMetricData() + cfg := goldendataset.DefaultCfg() + cfg.PtVal = 2 + actual := goldendataset.MetricDataFromCfg(cfg) + diffs := diffMetricData(expected, actual) + assert.Len(t, diffs, 1) +} + +func TestDifferentNumPts(t *testing.T) { + expected := goldendataset.DefaultMetricData() + cfg := goldendataset.DefaultCfg() + cfg.NumPtsPerMetric = 2 + actual := goldendataset.MetricDataFromCfg(cfg) + diffs := diffMetricData(expected, actual) + assert.Len(t, diffs, 1) +} + +func TestDifferentPtTypes(t *testing.T) { + expected := goldendataset.DefaultMetricData() + cfg := goldendataset.DefaultCfg() + cfg.MetricDescriptorType = pdata.MetricDataTypeDoubleGauge + actual := goldendataset.MetricDataFromCfg(cfg) + diffs := diffMetricData(expected, actual) + assert.Len(t, diffs, 1) +} + +func TestDoubleHistogram(t *testing.T) { + cfg1 := goldendataset.DefaultCfg() + cfg1.MetricDescriptorType = pdata.MetricDataTypeDoubleHistogram + expected := goldendataset.MetricDataFromCfg(cfg1) + cfg2 := goldendataset.DefaultCfg() + cfg2.MetricDescriptorType = pdata.MetricDataTypeDoubleHistogram + cfg2.PtVal = 2 + actual := goldendataset.MetricDataFromCfg(cfg2) + diffs := diffMetricData(expected, actual) + assert.Len(t, diffs, 3) +} + +func TestIntHistogram(t *testing.T) { + cfg1 := goldendataset.DefaultCfg() + cfg1.MetricDescriptorType = pdata.MetricDataTypeIntHistogram + expected := goldendataset.MetricDataFromCfg(cfg1) + cfg2 := goldendataset.DefaultCfg() + cfg2.MetricDescriptorType = pdata.MetricDataTypeIntHistogram + cfg2.PtVal = 2 + actual := goldendataset.MetricDataFromCfg(cfg2) + diffs := diffMetricData(expected, actual) + assert.Len(t, diffs, 3) +} + +func TestPDMToPDRM(t *testing.T) { + md := pdata.NewMetrics() + md.ResourceMetrics().Resize(2) + rms := pdmToPDRM([]pdata.Metrics{md}) + require.Len(t, rms, 2) +} diff --git a/internal/otel_collector/testbed/correctness/metrics/metric_index.go b/internal/otel_collector/testbed/correctness/metrics/metric_index.go new file mode 100644 index 00000000000..a07f9b39a14 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/metric_index.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +type metricReceived struct { + pdm pdata.Metrics + received bool +} + +type metricsReceivedIndex struct { + m map[string]*metricReceived +} + +func newMetricsReceivedIndex(pdms []pdata.Metrics) *metricsReceivedIndex { + mi := &metricsReceivedIndex{m: map[string]*metricReceived{}} + for _, pdm := range pdms { + metrics := pdm.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics() + name := metrics.At(0).Name() + mi.m[name] = &metricReceived{pdm: pdm} + } + return mi +} + +func (mi *metricsReceivedIndex) lookup(name string) (*metricReceived, bool) { + mr, ok := mi.m[name] + return mr, ok +} + +func (mi *metricsReceivedIndex) allReceived() bool { + for _, m := range mi.m { + if !m.received { + return false + } + } + return true +} diff --git a/internal/otel_collector/testbed/correctness/metrics/metric_supplier.go b/internal/otel_collector/testbed/correctness/metrics/metric_supplier.go new file mode 100644 index 00000000000..d1b01510e32 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/metric_supplier.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +type metricSupplier struct { + pdms []pdata.Metrics + currIdx int +} + +func newMetricSupplier(pdms []pdata.Metrics) *metricSupplier { + return &metricSupplier{pdms: pdms} +} + +func (p *metricSupplier) nextMetrics() (pdm pdata.Metrics, done bool) { + if p.currIdx == len(p.pdms) { + return pdata.Metrics{}, true + } + pdm = p.pdms[p.currIdx] + p.currIdx++ + return pdm, false +} diff --git a/internal/otel_collector/testbed/correctness/metrics/metrics_correctness_test.go b/internal/otel_collector/testbed/correctness/metrics/metrics_correctness_test.go new file mode 100644 index 00000000000..fd9368a48aa --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/metrics_correctness_test.go @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/testbed/correctness" + "go.opentelemetry.io/collector/testbed/testbed" +) + +func TestMetricsGoldenData(t *testing.T) { + tests, err := correctness.LoadPictOutputPipelineDefs("../testdata/generated_pict_pairs_metrics_pipeline.txt") + require.NoError(t, err) + for _, test := range tests { + test.TestName = fmt.Sprintf("%s-%s", test.Receiver, test.Exporter) + test.DataSender = correctness.ConstructMetricsSender(t, test.Receiver) + test.DataReceiver = correctness.ConstructReceiver(t, test.Exporter) + t.Run(test.TestName, func(t *testing.T) { + testWithMetricsGoldenDataset(t, test.DataSender.(testbed.MetricDataSender), test.DataReceiver) + }) + } +} + +func testWithMetricsGoldenDataset(t *testing.T, sender testbed.MetricDataSender, receiver testbed.DataReceiver) { + mds := getTestMetrics(t) + accumulator := newDiffAccumulator() + h := newTestHarness( + t, + newMetricSupplier(mds), + newMetricsReceivedIndex(mds), + sender, + accumulator, + ) + tc := newCorrectnessTestCase(t, sender, receiver, h) + + tc.startTestbedReceiver() + tc.startCollector() + tc.startTestbedSender() + + tc.sendFirstMetric() + tc.waitForAllMetrics() + + tc.stopTestbedReceiver() + tc.stopCollector() + + if accumulator.foundDiffs { + t.Fail() + } +} + +func getTestMetrics(t *testing.T) []pdata.Metrics { + const file = "../../../internal/goldendataset/testdata/generated_pict_pairs_metrics.txt" + mds, err := goldendataset.GenerateMetricDatas(file) + require.NoError(t, err) + return mds +} + +type diffAccumulator struct { + foundDiffs bool +} + +var _ diffConsumer = (*diffAccumulator)(nil) + +func newDiffAccumulator() *diffAccumulator { + return &diffAccumulator{} +} + +func (d *diffAccumulator) accept(metricName string, diffs []*MetricDiff) { + if len(diffs) > 0 { + d.foundDiffs = true + log.Printf("Found diffs for [%v]\n%v", metricName, diffs) + } +} diff --git a/internal/otel_collector/testbed/correctness/metrics/metrics_test_harness.go b/internal/otel_collector/testbed/correctness/metrics/metrics_test_harness.go new file mode 100644 index 00000000000..40296a8d566 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/metrics_test_harness.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testbed/testbed" +) + +// testHarness listens for datapoints from the receiver to which it is attached +// and when it receives one, it compares it to the datapoint that was previously +// sent out. It then sends the next datapoint, if there is one. +type testHarness struct { + t *testing.T + metricSupplier *metricSupplier + metricIndex *metricsReceivedIndex + sender testbed.MetricDataSender + currPDM pdata.Metrics + diffConsumer diffConsumer + outOfMetrics bool + allMetricsReceived chan struct{} +} + +type diffConsumer interface { + accept(string, []*MetricDiff) +} + +func newTestHarness( + t *testing.T, + s *metricSupplier, + mi *metricsReceivedIndex, + ds testbed.MetricDataSender, + diffConsumer diffConsumer, +) *testHarness { + return &testHarness{ + t: t, + metricSupplier: s, + metricIndex: mi, + sender: ds, + diffConsumer: diffConsumer, + allMetricsReceived: make(chan struct{}), + } +} + +func (h *testHarness) ConsumeMetrics(_ context.Context, pdm pdata.Metrics) error { + h.compare(pdm) + if h.metricIndex.allReceived() { + close(h.allMetricsReceived) + } + if !h.outOfMetrics { + h.sendNextMetric() + } + return nil +} + +func (h *testHarness) compare(pdm pdata.Metrics) { + pdms := pdm.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics() + var diffs []*MetricDiff + for i := 0; i < pdms.Len(); i++ { + pdmRecd := pdms.At(i) + metricName := pdmRecd.Name() + metric, found := h.metricIndex.lookup(metricName) + if !found { + h.diffConsumer.accept(metricName, []*MetricDiff{{ + ExpectedValue: metricName, + Msg: "Metric name not found in index", + }}) + } + if !metric.received { + metric.received = true + sent := metric.pdm + pdmExpected := sent.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + diffs = DiffMetric( + diffs, + pdmExpected, + pdmRecd, + ) + h.diffConsumer.accept(metricName, diffs) + } + } +} + +func (h *testHarness) sendNextMetric() { + h.currPDM, h.outOfMetrics = h.metricSupplier.nextMetrics() + if h.outOfMetrics { + return + } + err := h.sender.ConsumeMetrics(context.Background(), h.currPDM) + require.NoError(h.t, err) +} diff --git a/internal/otel_collector/testbed/correctness/metrics/results_dir.go b/internal/otel_collector/testbed/correctness/metrics/results_dir.go new file mode 100644 index 00000000000..2ad05ed36a8 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/metrics/results_dir.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "os" + "path" + "path/filepath" +) + +type resultsDir struct { + dir string +} + +func newResultsDir(dirName string) (*resultsDir, error) { + dir, err := filepath.Abs(path.Join("results", dirName)) + if err != nil { + return nil, err + } + return &resultsDir{dir: dir}, nil +} + +func (d *resultsDir) mkDir() error { + return os.MkdirAll(d.dir, os.ModePerm) +} + +func (d *resultsDir) fullPath(name string) (string, error) { + return filepath.Abs(path.Join(d.dir, name)) +} diff --git a/internal/otel_collector/testbed/correctness/testdata/generated_pict_pairs_metrics_pipeline.txt b/internal/otel_collector/testbed/correctness/testdata/generated_pict_pairs_metrics_pipeline.txt new file mode 100644 index 00000000000..1db11505cd5 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/testdata/generated_pict_pairs_metrics_pipeline.txt @@ -0,0 +1,2 @@ +Receiver Exporter +otlp otlp diff --git a/internal/otel_collector/testbed/correctness/testdata/pict_input_metrics_pipeline.txt b/internal/otel_collector/testbed/correctness/testdata/pict_input_metrics_pipeline.txt new file mode 100644 index 00000000000..648e967e482 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/testdata/pict_input_metrics_pipeline.txt @@ -0,0 +1,2 @@ +Receiver: otlp +Exporter: otlp diff --git a/internal/otel_collector/testbed/correctness/traces/.gitignore b/internal/otel_collector/testbed/correctness/traces/.gitignore new file mode 100644 index 00000000000..a61c5ef81e8 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/traces/.gitignore @@ -0,0 +1,2 @@ +results/* +!results/BASELINE.md diff --git a/internal/otel_collector/testbed/correctness/traces/correctness_test.go b/internal/otel_collector/testbed/correctness/traces/correctness_test.go new file mode 100644 index 00000000000..6d712dd7d45 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/traces/correctness_test.go @@ -0,0 +1,106 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package traces + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/service/defaultcomponents" + "go.opentelemetry.io/collector/testbed/correctness" + "go.opentelemetry.io/collector/testbed/testbed" +) + +var correctnessResults testbed.TestResultsSummary = &testbed.CorrectnessResults{} + +func TestMain(m *testing.M) { + testbed.DoTestMain(m, correctnessResults) +} + +func TestTracingGoldenData(t *testing.T) { + tests, err := correctness.LoadPictOutputPipelineDefs("testdata/generated_pict_pairs_traces_pipeline.txt") + require.NoError(t, err) + processors := map[string]string{ + "batch": ` + batch: + send_batch_size: 1024 +`, + } + for _, test := range tests { + test.TestName = fmt.Sprintf("%s-%s", test.Receiver, test.Exporter) + test.DataSender = correctness.ConstructTraceSender(t, test.Receiver) + test.DataReceiver = correctness.ConstructReceiver(t, test.Exporter) + t.Run(test.TestName, func(t *testing.T) { + testWithTracingGoldenDataset(t, test.DataSender, test.DataReceiver, test.ResourceSpec, processors) + }) + } +} + +func testWithTracingGoldenDataset( + t *testing.T, + sender testbed.DataSender, + receiver testbed.DataReceiver, + resourceSpec testbed.ResourceSpec, + processors map[string]string, +) { + dataProvider := testbed.NewGoldenDataProvider( + "../../../internal/goldendataset/testdata/generated_pict_pairs_traces.txt", + "../../../internal/goldendataset/testdata/generated_pict_pairs_spans.txt", + "", + 161803) + factories, err := defaultcomponents.Components() + require.NoError(t, err, "default components resulted in: %v", err) + runner := testbed.NewInProcessCollector(factories) + validator := testbed.NewCorrectTestValidator(dataProvider) + config := correctness.CreateConfigYaml(sender, receiver, processors, "traces") + configCleanup, cfgErr := runner.PrepareConfig(config) + require.NoError(t, cfgErr, "collector configuration resulted in: %v", cfgErr) + defer configCleanup() + tc := testbed.NewTestCase( + t, + dataProvider, + sender, + receiver, + runner, + validator, + correctnessResults, + ) + defer tc.Stop() + + tc.SetResourceLimits(resourceSpec) + tc.EnableRecording() + tc.StartBackend() + tc.StartAgent("--metrics-level=NONE") + + tc.StartLoad(testbed.LoadOptions{ + DataItemsPerSecond: 1024, + ItemsPerBatch: 1, + }) + + duration := time.Second + tc.Sleep(duration) + + tc.StopLoad() + + tc.WaitForN(func() bool { return tc.LoadGenerator.DataItemsSent() == tc.MockBackend.DataItemsReceived() }, + duration*3, "all data items received") + + tc.StopAgent() + + tc.ValidateData() +} diff --git a/internal/otel_collector/testbed/correctness/traces/runtests.sh b/internal/otel_collector/testbed/correctness/traces/runtests.sh new file mode 100644 index 00000000000..f4dc30921e5 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/traces/runtests.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +SED="sed" + +PASS_COLOR=$(printf "\033[32mPASS\033[0m") +FAIL_COLOR=$(printf "\033[31mFAIL\033[0m") +TEST_COLORIZE="${SED} 's/PASS/${PASS_COLOR}/' | ${SED} 's/FAIL/${FAIL_COLOR}/'" +echo ${TEST_ARGS} +mkdir -p results +RUN_TESTBED=1 go test -v ${TEST_ARGS} 2>&1 | tee results/testoutput.log | bash -c "${TEST_COLORIZE}" + +testStatus=${PIPESTATUS[0]} + +mkdir -p results/junit +go-junit-report < results/testoutput.log > results/junit/results.xml + +bash -c "cat results/CORRECTNESSRESULTS.md | ${TEST_COLORIZE}" + +exit ${testStatus} diff --git a/internal/otel_collector/testbed/correctness/traces/testdata/generated_pict_pairs_traces_pipeline.txt b/internal/otel_collector/testbed/correctness/traces/testdata/generated_pict_pairs_traces_pipeline.txt new file mode 100644 index 00000000000..af31f5aa370 --- /dev/null +++ b/internal/otel_collector/testbed/correctness/traces/testdata/generated_pict_pairs_traces_pipeline.txt @@ -0,0 +1,17 @@ +Receiver Exporter +otlp jaeger +zipkin opencensus +otlp opencensus +jaeger opencensus +opencensus jaeger +zipkin otlp +jaeger jaeger +opencensus opencensus +otlp zipkin +jaeger zipkin +opencensus zipkin +zipkin jaeger +otlp otlp +jaeger otlp +opencensus otlp +zipkin zipkin diff --git a/internal/otel_collector/testbed/correctness/traces/testdata/pict_input_traces_pipeline.txt b/internal/otel_collector/testbed/correctness/traces/testdata/pict_input_traces_pipeline.txt new file mode 100644 index 00000000000..05efad8fdea --- /dev/null +++ b/internal/otel_collector/testbed/correctness/traces/testdata/pict_input_traces_pipeline.txt @@ -0,0 +1,2 @@ +Receiver: jaeger, opencensus, otlp, zipkin +Exporter: jaeger, opencensus, otlp, zipkin diff --git a/internal/otel_collector/testbed/correctness/utils.go b/internal/otel_collector/testbed/correctness/utils.go new file mode 100644 index 00000000000..7f3278370ac --- /dev/null +++ b/internal/otel_collector/testbed/correctness/utils.go @@ -0,0 +1,175 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package correctness + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "go.opentelemetry.io/collector/testbed/testbed" +) + +// CreateConfigYaml creates a yaml config for an otel collector given a testbed sender, testbed receiver, any +// processors, and a pipeline type. A collector created from the resulting yaml string should be able to talk +// the specified sender and receiver. +func CreateConfigYaml( + sender testbed.DataSender, + receiver testbed.DataReceiver, + processors map[string]string, + pipelineType string, +) string { + + // Prepare extra processor config section and comma-separated list of extra processor + // names to use in corresponding "processors" settings. + processorsSections := "" + processorsList := "" + if len(processors) > 0 { + first := true + for name, cfg := range processors { + processorsSections += cfg + "\n" + if !first { + processorsList += "," + } + processorsList += name + first = false + } + } + + format := ` +receivers:%v +exporters:%v +processors: + %s + +extensions: + +service: + extensions: + pipelines: + %s: + receivers: [%v] + processors: [%s] + exporters: [%v] +` + + return fmt.Sprintf( + format, + sender.GenConfigYAMLStr(), + receiver.GenConfigYAMLStr(), + processorsSections, + pipelineType, + sender.ProtocolName(), + processorsList, + receiver.ProtocolName(), + ) +} + +// PipelineDef holds the information necessary to run a single testbed configuration. +type PipelineDef struct { + Receiver string + Exporter string + TestName string + DataSender testbed.DataSender + DataReceiver testbed.DataReceiver + ResourceSpec testbed.ResourceSpec +} + +// LoadPictOutputPipelineDefs generates a slice of PipelineDefs from the passed-in generated PICT file. The +// result should be a set of PipelineDefs that covers all possible pipeline configurations. +func LoadPictOutputPipelineDefs(fileName string) ([]PipelineDef, error) { + file, err := os.Open(filepath.Clean(fileName)) + if err != nil { + return nil, err + } + defer func() { + cerr := file.Close() + if err == nil { + err = cerr + } + }() + + defs := make([]PipelineDef, 0) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + s := strings.Split(scanner.Text(), "\t") + if s[0] == "Receiver" { + continue + } + + var aDef PipelineDef + aDef.Receiver, aDef.Exporter = s[0], s[1] + defs = append(defs, aDef) + } + + return defs, err +} + +// ConstructTraceSender creates a testbed trace sender from the passed-in trace sender identifier. +func ConstructTraceSender(t *testing.T, receiver string) testbed.DataSender { + var sender testbed.DataSender + switch receiver { + case "otlp": + sender = testbed.NewOTLPTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + case "opencensus": + sender = testbed.NewOCTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + case "jaeger": + sender = testbed.NewJaegerGRPCDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + case "zipkin": + sender = testbed.NewZipkinDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + default: + t.Errorf("unknown receiver type: %s", receiver) + } + return sender +} + +// ConstructMetricsSender creates a testbed metrics sender from the passed-in metrics sender identifier. +func ConstructMetricsSender(t *testing.T, receiver string) testbed.MetricDataSender { + var sender testbed.MetricDataSender + switch receiver { + case "otlp": + sender = testbed.NewOTLPMetricDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + case "opencensus": + sender = testbed.NewOCMetricDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + case "prometheus": + sender = testbed.NewPrometheusDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + default: + t.Errorf("unknown receiver type: %s", receiver) + } + return sender +} + +// ConstructReceiver creates a testbed receiver from the passed-in recevier identifier. +func ConstructReceiver(t *testing.T, exporter string) testbed.DataReceiver { + var receiver testbed.DataReceiver + switch exporter { + case "otlp": + receiver = testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)) + case "opencensus": + receiver = testbed.NewOCDataReceiver(testbed.GetAvailablePort(t)) + case "jaeger": + receiver = testbed.NewJaegerDataReceiver(testbed.GetAvailablePort(t)) + case "zipkin": + receiver = testbed.NewZipkinDataReceiver(testbed.GetAvailablePort(t)) + case "prometheus": + receiver = testbed.NewPrometheusDataReceiver(testbed.GetAvailablePort(t)) + default: + t.Errorf("unknown exporter type: %s", exporter) + } + return receiver +} diff --git a/internal/otel_collector/testbed/correctness_result.png b/internal/otel_collector/testbed/correctness_result.png new file mode 100644 index 0000000000000000000000000000000000000000..c9046855597a788c2d3ccccc270d603d1866a37a GIT binary patch literal 2161669 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

o_`^$~wtfk9c^A(rZ3t$uo9`R+({3BHk% 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ðTdvGH{_q!UtEs2auLY9I)Q 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

5&!;Z*4;2Yn}(^#)!FCt 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�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%0D5O|Ig(1er>d>{%810 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 z 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

OIB{I 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 z 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

4oz*wLtpBgef;k`ALS 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


wK!fZ%*KL@D?gswA16admm#0J7n{ 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

up^^A| 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%0AnD}N=d$KX#*uLS`E8=DrVw9-A_h|3__!VE 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


H?oo{(c#l%qI9p5s87IR`_#@FgYU@TG0OP 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 LuRul6#ZVakvVb9`)R-gt^gX6W*O3rpao>Lc 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
    T`|(P=4JB6~ne<0B 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 meIXSL&V2*W%ppaB9& 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 literal 0 HcmV?d00001 diff --git a/internal/otel_collector/testbed/testbed/.gitignore b/internal/otel_collector/testbed/testbed/.gitignore new file mode 100644 index 00000000000..9835eb192b0 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/.gitignore @@ -0,0 +1 @@ +mockbackend.log diff --git a/internal/otel_collector/testbed/testbed/child_process.go b/internal/otel_collector/testbed/testbed/child_process.go new file mode 100644 index 00000000000..18e93bf1867 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/child_process.go @@ -0,0 +1,512 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "sync" + "syscall" + "text/template" + "time" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/process" + "go.uber.org/atomic" +) + +// ResourceSpec is a resource consumption specification. +type ResourceSpec struct { + // Percentage of one core the process is expected to consume at most. + // Test is aborted and failed if consumption during + // ResourceCheckPeriod exceeds this number. If 0 the CPU + // consumption is not monitored and does not affect the test result. + ExpectedMaxCPU uint32 + + // Maximum RAM in MiB the process is expected to consume. + // Test is aborted and failed if consumption exceeds this number. + // If 0 memory consumption is not monitored and does not affect + // the test result. + ExpectedMaxRAM uint32 + + // Period during which CPU and RAM of the process are measured. + // Bigger numbers will result in more averaging of short spikes. + ResourceCheckPeriod time.Duration +} + +// isSpecified returns true if any part of ResourceSpec is specified, +// i.e. has non-zero value. +func (rs *ResourceSpec) isSpecified() bool { + return rs != nil && (rs.ExpectedMaxCPU != 0 || rs.ExpectedMaxRAM != 0) +} + +// ChildProcess implements the OtelcolRunner interface as a child process on the same machine executing +// the test. The process can be monitored and the output of which will be written to a log file. +type ChildProcess struct { + // Path to agent executable. If unset the default executable in + // bin/otelcol_{{.GOOS}}_{{.GOARCH}} will be used. + // Can be set for example to use the unstable executable for a specific test. + AgentExePath string + + // Descriptive name of the process + name string + + // Config file name + configFileName string + + // Command to execute + cmd *exec.Cmd + + // WaitGroup for copying process output + outputWG sync.WaitGroup + + // Various starting/stopping flags + isStarted bool + stopOnce sync.Once + isStopped bool + doneSignal chan struct{} + + // Resource specification that must be monitored for. + resourceSpec *ResourceSpec + + // Process monitoring data. + processMon *process.Process + + // Time when process was started. + startTime time.Time + + // Last tick time we monitored the process. + lastElapsedTime time.Time + + // Process times that were fetched on last monitoring tick. + lastProcessTimes *cpu.TimesStat + + // Current RAM RSS in MiBs + ramMiBCur atomic.Uint32 + + // Current CPU percentage times 1000 (we use scaling since we have to use int for atomic operations). + cpuPercentX1000Cur atomic.Uint32 + + // Maximum CPU seen + cpuPercentMax float64 + + // Number of memory measurements + memProbeCount int + + // Cumulative RAM RSS in MiBs + ramMiBTotal uint64 + + // Maximum RAM seen + ramMiBMax uint32 +} + +type StartParams struct { + Name string + LogFilePath string + CmdArgs []string + resourceSpec *ResourceSpec +} + +type ResourceConsumption struct { + CPUPercentAvg float64 + CPUPercentMax float64 + RAMMiBAvg uint32 + RAMMiBMax uint32 +} + +func (cp *ChildProcess) PrepareConfig(configStr string) (configCleanup func(), err error) { + configCleanup = func() { + // NoOp + } + var file *os.File + file, err = ioutil.TempFile("", "agent*.yaml") + if err != nil { + log.Printf("%s", err) + return configCleanup, err + } + + defer func() { + errClose := file.Close() + if errClose != nil { + log.Printf("%s", errClose) + } + }() + + if _, err = file.WriteString(configStr); err != nil { + log.Printf("%s", err) + return configCleanup, err + } + cp.configFileName = file.Name() + configCleanup = func() { + os.Remove(cp.configFileName) + } + return configCleanup, err +} + +func expandExeFileName(exeName string) string { + cfgTemplate, err := template.New("").Parse(exeName) + if err != nil { + log.Fatalf("Template failed to parse exe name %q: %s", + exeName, err.Error()) + } + + templateVars := struct { + GOOS string + GOARCH string + }{ + GOOS: runtime.GOOS, + GOARCH: runtime.GOARCH, + } + var buf bytes.Buffer + if err = cfgTemplate.Execute(&buf, templateVars); err != nil { + log.Fatalf("Configuration template failed to run on exe name %q: %s", + exeName, err.Error()) + } + + return buf.String() +} + +// start a child process. +// +// cp.AgentExePath defines the executable to run. If unspecified +// "../../bin/otelcol_{{.GOOS}}_{{.GOARCH}}" will be used. +// {{.GOOS}} and {{.GOARCH}} will be expanded to the current OS and ARCH correspondingly. +// +// Parameters: +// name is the human readable name of the process (e.g. "Agent"), used for logging. +// logFilePath is the file path to write the standard output and standard error of +// the process to. +// cmdArgs is the command line arguments to pass to the process. +func (cp *ChildProcess) Start(params StartParams) error { + + cp.name = params.Name + cp.doneSignal = make(chan struct{}) + cp.resourceSpec = params.resourceSpec + + if cp.AgentExePath == "" { + cp.AgentExePath = GlobalConfig.DefaultAgentExeRelativeFile + } + exePath := expandExeFileName(cp.AgentExePath) + exePath, err := filepath.Abs(exePath) + if err != nil { + return err + } + + log.Printf("Starting %s (%s)", cp.name, exePath) + + // Prepare log file + logFile, err := os.Create(params.LogFilePath) + if err != nil { + return fmt.Errorf("cannot create %s: %s", params.LogFilePath, err.Error()) + } + log.Printf("Writing %s log to %s", cp.name, params.LogFilePath) + + // Prepare to start the process. + // #nosec + args := params.CmdArgs + if !containsConfig(args) { + if cp.configFileName == "" { + configFile := path.Join("testdata", "agent-config.yaml") + cp.configFileName, err = filepath.Abs(configFile) + if err != nil { + return err + } + } + args = append(args, "--config") + args = append(args, cp.configFileName) + } + cp.cmd = exec.Command(exePath, args...) + + // Capture standard output and standard error. + stdoutIn, err := cp.cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("cannot capture stdout of %s: %s", exePath, err.Error()) + } + stderrIn, err := cp.cmd.StderrPipe() + if err != nil { + return fmt.Errorf("cannot capture stderr of %s: %s", exePath, err.Error()) + } + + // Start the process. + if err = cp.cmd.Start(); err != nil { + return fmt.Errorf("cannot start executable at %s: %s", exePath, err.Error()) + } + + cp.startTime = time.Now() + cp.isStarted = true + + log.Printf("%s running, pid=%d", cp.name, cp.cmd.Process.Pid) + + // Create a WaitGroup that waits for both outputs to be finished copying. + cp.outputWG.Add(2) + + // Begin copying outputs. + go func() { + _, _ = io.Copy(logFile, stdoutIn) + cp.outputWG.Done() + }() + go func() { + _, _ = io.Copy(logFile, stderrIn) + cp.outputWG.Done() + }() + + return err +} + +func (cp *ChildProcess) Stop() (stopped bool, err error) { + if !cp.isStarted || cp.isStopped { + return false, nil + } + cp.stopOnce.Do(func() { + + if !cp.isStarted { + // Process wasn't started, nothing to stop. + return + } + + cp.isStopped = true + + log.Printf("Gracefully terminating %s pid=%d, sending SIGTEM...", cp.name, cp.cmd.Process.Pid) + + // Notify resource monitor to stop. + close(cp.doneSignal) + + // Gracefully signal process to stop. + if err = cp.cmd.Process.Signal(syscall.SIGTERM); err != nil { + log.Printf("Cannot send SIGTEM: %s", err.Error()) + } + + finished := make(chan struct{}) + + // Setup a goroutine to wait a while for process to finish and send kill signal + // to the process if it doesn't finish. + go func() { + // Wait 10 seconds. + t := time.After(10 * time.Second) + select { + case <-t: + // Time is out. Kill the process. + log.Printf("%s pid=%d is not responding to SIGTERM. Sending SIGKILL to kill forcedly.", + cp.name, cp.cmd.Process.Pid) + if err = cp.cmd.Process.Signal(syscall.SIGKILL); err != nil { + log.Printf("Cannot send SIGKILL: %s", err.Error()) + } + case <-finished: + // Process is successfully finished. + } + }() + + // Wait for output to be fully copied. + cp.outputWG.Wait() + + // Wait for process to terminate + err = cp.cmd.Wait() + + // Let goroutine know process is finished. + close(finished) + + // Set resource consumption stats to 0 + cp.ramMiBCur.Store(0) + cp.cpuPercentX1000Cur.Store(0) + + log.Printf("%s process stopped, exit code=%d", cp.name, cp.cmd.ProcessState.ExitCode()) + + if err != nil { + log.Printf("%s execution failed: %s", cp.name, err.Error()) + } + }) + stopped = true + return stopped, err +} + +func (cp *ChildProcess) WatchResourceConsumption() error { + if !cp.resourceSpec.isSpecified() { + // Resource monitoring is not enabled. + return nil + } + + var err error + cp.processMon, err = process.NewProcess(int32(cp.cmd.Process.Pid)) + if err != nil { + return fmt.Errorf("cannot monitor process %d: %s", + cp.cmd.Process.Pid, err.Error()) + } + + cp.fetchRAMUsage() + + // Begin measuring elapsed and process CPU times. + cp.lastElapsedTime = time.Now() + cp.lastProcessTimes, err = cp.processMon.Times() + if err != nil { + return fmt.Errorf("cannot get process times for %d: %s", + cp.cmd.Process.Pid, err.Error()) + } + + // Measure every ResourceCheckPeriod. + ticker := time.NewTicker(cp.resourceSpec.ResourceCheckPeriod) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + cp.fetchRAMUsage() + cp.fetchCPUUsage() + + if err := cp.checkAllowedResourceUsage(); err != nil { + cp.Stop() + return err + } + + case <-cp.doneSignal: + log.Printf("Stopping process monitor.") + return nil + } + } +} + +func (cp *ChildProcess) GetProcessMon() *process.Process { + return cp.processMon +} + +func (cp *ChildProcess) fetchRAMUsage() { + // Get process memory and CPU times + mi, err := cp.processMon.MemoryInfo() + if err != nil { + log.Printf("cannot get process memory for %d: %s", + cp.cmd.Process.Pid, err.Error()) + return + } + + // Calculate RSS in MiBs. + ramMiBCur := uint32(mi.RSS / mibibyte) + + // Calculate aggregates. + cp.memProbeCount++ + cp.ramMiBTotal += uint64(ramMiBCur) + if ramMiBCur > cp.ramMiBMax { + cp.ramMiBMax = ramMiBCur + } + + // Store current usage. + cp.ramMiBCur.Store(ramMiBCur) +} + +func (cp *ChildProcess) fetchCPUUsage() { + times, err := cp.processMon.Times() + if err != nil { + log.Printf("cannot get process times for %d: %s", + cp.cmd.Process.Pid, err.Error()) + return + } + + now := time.Now() + + // Calculate elapsed and process CPU time deltas in seconds + deltaElapsedTime := now.Sub(cp.lastElapsedTime).Seconds() + deltaCPUTime := times.Total() - cp.lastProcessTimes.Total() + + cp.lastProcessTimes = times + cp.lastElapsedTime = now + + // Calculate CPU usage percentage in elapsed period. + cpuPercent := deltaCPUTime * 100 / deltaElapsedTime + if cpuPercent > cp.cpuPercentMax { + cp.cpuPercentMax = cpuPercent + } + + curCPUPercentageX1000 := uint32(cpuPercent * 1000) + + // Store current usage. + cp.cpuPercentX1000Cur.Store(curCPUPercentageX1000) +} + +func (cp *ChildProcess) checkAllowedResourceUsage() error { + // Check if current CPU usage exceeds expected. + var errMsg string + if cp.resourceSpec.ExpectedMaxCPU != 0 && cp.cpuPercentX1000Cur.Load()/1000 > cp.resourceSpec.ExpectedMaxCPU { + errMsg = fmt.Sprintf("CPU consumption is %.1f%%, max expected is %d%%", + float64(cp.cpuPercentX1000Cur.Load())/1000.0, cp.resourceSpec.ExpectedMaxCPU) + } + + // Check if current RAM usage exceeds expected. + if cp.resourceSpec.ExpectedMaxRAM != 0 && cp.ramMiBCur.Load() > cp.resourceSpec.ExpectedMaxRAM { + errMsg = fmt.Sprintf("RAM consumption is %s MiB, max expected is %d MiB", + cp.ramMiBCur.String(), cp.resourceSpec.ExpectedMaxRAM) + } + + if errMsg == "" { + return nil + } + + log.Printf("Performance error: %s", errMsg) + + return errors.New(errMsg) +} + +// GetResourceConsumption returns resource consumption as a string +func (cp *ChildProcess) GetResourceConsumption() string { + if !cp.resourceSpec.isSpecified() { + // Monitoring is not enabled. + return "" + } + + curRSSMib := cp.ramMiBCur.Load() + curCPUPercentageX1000 := cp.cpuPercentX1000Cur.Load() + + return fmt.Sprintf("%s RAM (RES):%4d MiB, CPU:%4.1f%%", cp.name, + curRSSMib, float64(curCPUPercentageX1000)/1000.0) +} + +// GetTotalConsumption returns total resource consumption since start of process +func (cp *ChildProcess) GetTotalConsumption() *ResourceConsumption { + rc := &ResourceConsumption{} + + if cp.processMon != nil { + // Get total elapsed time since process start + elapsedDuration := cp.lastElapsedTime.Sub(cp.startTime).Seconds() + + if elapsedDuration > 0 { + // Calculate average CPU usage since start of process + rc.CPUPercentAvg = cp.lastProcessTimes.Total() / elapsedDuration * 100.0 + } + rc.CPUPercentMax = cp.cpuPercentMax + + if cp.memProbeCount > 0 { + // Calculate average RAM usage by averaging all RAM measurements + rc.RAMMiBAvg = uint32(cp.ramMiBTotal / uint64(cp.memProbeCount)) + } + rc.RAMMiBMax = cp.ramMiBMax + } + + return rc +} + +func containsConfig(s []string) bool { + for _, a := range s { + if a == "--config" { + return true + } + } + return false +} diff --git a/internal/otel_collector/testbed/testbed/data_providers.go b/internal/otel_collector/testbed/testbed/data_providers.go new file mode 100644 index 00000000000..0767135a9dd --- /dev/null +++ b/internal/otel_collector/testbed/testbed/data_providers.go @@ -0,0 +1,310 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "encoding/binary" + "fmt" + "io" + "log" + "math/rand" + "strconv" + "time" + + "go.uber.org/atomic" + + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/goldendataset" +) + +// DataProvider defines the interface for generators of test data used to drive various end-to-end tests. +type DataProvider interface { + // SetLoadGeneratorCounters supplies pointers to LoadGenerator counters. + // The data provider implementation should increment these as it generates data. + SetLoadGeneratorCounters(batchesGenerated *atomic.Uint64, dataItemsGenerated *atomic.Uint64) + // GenerateTraces returns an internal Traces instance with an OTLP ResourceSpans slice populated with test data. + GenerateTraces() (pdata.Traces, bool) + // GenerateMetrics returns an internal MetricData instance with an OTLP ResourceMetrics slice of test data. + GenerateMetrics() (pdata.Metrics, bool) + // GetGeneratedSpan returns the generated Span matching the provided traceId and spanId or else nil if no match found. + GetGeneratedSpan(traceID pdata.TraceID, spanID pdata.SpanID) *otlptrace.Span + // GenerateLogs returns the internal pdata.Logs format + GenerateLogs() (pdata.Logs, bool) +} + +// PerfTestDataProvider in an implementation of the DataProvider for use in performance tests. +// Tracing IDs are based on the incremented batch and data items counters. +type PerfTestDataProvider struct { + options LoadOptions + batchesGenerated *atomic.Uint64 + dataItemsGenerated *atomic.Uint64 +} + +// NewPerfTestDataProvider creates an instance of PerfTestDataProvider which generates test data based on the sizes +// specified in the supplied LoadOptions. +func NewPerfTestDataProvider(options LoadOptions) *PerfTestDataProvider { + return &PerfTestDataProvider{ + options: options, + } +} + +func (dp *PerfTestDataProvider) SetLoadGeneratorCounters(batchesGenerated *atomic.Uint64, dataItemsGenerated *atomic.Uint64) { + dp.batchesGenerated = batchesGenerated + dp.dataItemsGenerated = dataItemsGenerated +} + +func (dp *PerfTestDataProvider) GenerateTraces() (pdata.Traces, bool) { + + traceData := pdata.NewTraces() + traceData.ResourceSpans().Resize(1) + ilss := traceData.ResourceSpans().At(0).InstrumentationLibrarySpans() + ilss.Resize(1) + spans := ilss.At(0).Spans() + spans.Resize(dp.options.ItemsPerBatch) + + traceID := dp.batchesGenerated.Inc() + for i := 0; i < dp.options.ItemsPerBatch; i++ { + + startTime := time.Now() + endTime := startTime.Add(time.Millisecond) + + spanID := dp.dataItemsGenerated.Inc() + + span := spans.At(i) + + // Create a span. + span.SetTraceID(GenerateSequentialTraceID(traceID)) + span.SetSpanID(GenerateSequentialSpanID(spanID)) + span.SetName("load-generator-span") + span.SetKind(pdata.SpanKindCLIENT) + attrs := span.Attributes() + attrs.UpsertInt("load_generator.span_seq_num", int64(spanID)) + attrs.UpsertInt("load_generator.trace_seq_num", int64(traceID)) + // Additional attributes. + for k, v := range dp.options.Attributes { + attrs.UpsertString(k, v) + } + span.SetStartTime(pdata.TimestampUnixNano(uint64(startTime.UnixNano()))) + span.SetEndTime(pdata.TimestampUnixNano(uint64(endTime.UnixNano()))) + } + return traceData, false +} + +func GenerateSequentialTraceID(id uint64) pdata.TraceID { + var traceID [16]byte + binary.PutUvarint(traceID[:], id) + return pdata.NewTraceID(traceID) +} + +func GenerateSequentialSpanID(id uint64) pdata.SpanID { + var spanID [8]byte + binary.PutUvarint(spanID[:], id) + return pdata.NewSpanID(spanID) +} + +func (dp *PerfTestDataProvider) GenerateMetrics() (pdata.Metrics, bool) { + + // Generate 7 data points per metric. + const dataPointsPerMetric = 7 + + md := pdata.NewMetrics() + md.ResourceMetrics().Resize(1) + md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().Resize(1) + if dp.options.Attributes != nil { + attrs := md.ResourceMetrics().At(0).Resource().Attributes() + attrs.InitEmptyWithCapacity(len(dp.options.Attributes)) + for k, v := range dp.options.Attributes { + attrs.UpsertString(k, v) + } + } + metrics := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics() + metrics.Resize(dp.options.ItemsPerBatch) + + for i := 0; i < dp.options.ItemsPerBatch; i++ { + metric := metrics.At(i) + metric.SetName("load_generator_" + strconv.Itoa(i)) + metric.SetDescription("Load Generator Counter #" + strconv.Itoa(i)) + metric.SetUnit("1") + metric.SetDataType(pdata.MetricDataTypeIntGauge) + + batchIndex := dp.batchesGenerated.Inc() + + dps := metric.IntGauge().DataPoints() + // Generate data points for the metric. + dps.Resize(dataPointsPerMetric) + for j := 0; j < dataPointsPerMetric; j++ { + dataPoint := dps.At(j) + dataPoint.SetStartTime(pdata.TimestampUnixNano(uint64(time.Now().UnixNano()))) + value := dp.dataItemsGenerated.Inc() + dataPoint.SetValue(int64(value)) + dataPoint.LabelsMap().InitFromMap(map[string]string{ + "item_index": "item_" + strconv.Itoa(j), + "batch_index": "batch_" + strconv.Itoa(int(batchIndex)), + }) + } + } + return md, false +} + +func (dp *PerfTestDataProvider) GetGeneratedSpan(pdata.TraceID, pdata.SpanID) *otlptrace.Span { + // function not supported for this data provider + return nil +} + +func (dp *PerfTestDataProvider) GenerateLogs() (pdata.Logs, bool) { + logs := pdata.NewLogs() + logs.ResourceLogs().Resize(1) + logs.ResourceLogs().At(0).InstrumentationLibraryLogs().Resize(1) + if dp.options.Attributes != nil { + attrs := logs.ResourceLogs().At(0).Resource().Attributes() + attrs.InitEmptyWithCapacity(len(dp.options.Attributes)) + for k, v := range dp.options.Attributes { + attrs.UpsertString(k, v) + } + } + logRecords := logs.ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs() + logRecords.Resize(dp.options.ItemsPerBatch) + + now := pdata.TimestampUnixNano(time.Now().UnixNano()) + + batchIndex := dp.batchesGenerated.Inc() + + for i := 0; i < dp.options.ItemsPerBatch; i++ { + itemIndex := dp.dataItemsGenerated.Inc() + record := logRecords.At(i) + record.SetSeverityNumber(pdata.SeverityNumberINFO3) + record.SetSeverityText("INFO3") + record.SetName("load_generator_" + strconv.Itoa(i)) + record.Body().SetStringVal("Load Generator Counter #" + strconv.Itoa(i)) + record.SetFlags(uint32(2)) + record.SetTimestamp(now) + + attrs := record.Attributes() + attrs.UpsertString("batch_index", "batch_"+strconv.Itoa(int(batchIndex))) + attrs.UpsertString("item_index", "item_"+strconv.Itoa(int(itemIndex))) + attrs.UpsertString("a", "test") + attrs.UpsertDouble("b", 5.0) + attrs.UpsertInt("c", 3) + attrs.UpsertBool("d", true) + } + return logs, false +} + +// GoldenDataProvider is an implementation of DataProvider for use in correctness tests. +// Provided data from the "Golden" dataset generated using pairwise combinatorial testing techniques. +type GoldenDataProvider struct { + tracePairsFile string + spanPairsFile string + random io.Reader + batchesGenerated *atomic.Uint64 + dataItemsGenerated *atomic.Uint64 + resourceSpans []*otlptrace.ResourceSpans + spansIndex int + spansMap map[string]*otlptrace.Span + + metricPairsFile string + metricsGenerated []pdata.Metrics + metricsIndex int +} + +// NewGoldenDataProvider creates a new instance of GoldenDataProvider which generates test data based +// on the pairwise combinations specified in the tracePairsFile and spanPairsFile input variables. +// The supplied randomSeed is used to initialize the random number generator used in generating tracing IDs. +func NewGoldenDataProvider(tracePairsFile string, spanPairsFile string, metricPairsFile string, randomSeed int64) *GoldenDataProvider { + return &GoldenDataProvider{ + tracePairsFile: tracePairsFile, + spanPairsFile: spanPairsFile, + metricPairsFile: metricPairsFile, + random: io.Reader(rand.New(rand.NewSource(randomSeed))), + } +} + +func (dp *GoldenDataProvider) SetLoadGeneratorCounters(batchesGenerated *atomic.Uint64, dataItemsGenerated *atomic.Uint64) { + dp.batchesGenerated = batchesGenerated + dp.dataItemsGenerated = dataItemsGenerated +} + +func (dp *GoldenDataProvider) GenerateTraces() (pdata.Traces, bool) { + if dp.resourceSpans == nil { + var err error + dp.resourceSpans, err = goldendataset.GenerateResourceSpans(dp.tracePairsFile, dp.spanPairsFile, dp.random) + if err != nil { + log.Printf("cannot generate traces: %s", err) + dp.resourceSpans = make([]*otlptrace.ResourceSpans, 0) + } + } + dp.batchesGenerated.Inc() + if dp.spansIndex >= len(dp.resourceSpans) { + return pdata.TracesFromOtlp(make([]*otlptrace.ResourceSpans, 0)), true + } + resourceSpans := make([]*otlptrace.ResourceSpans, 1) + resourceSpans[0] = dp.resourceSpans[dp.spansIndex] + dp.spansIndex++ + spanCount := uint64(0) + for _, libSpans := range resourceSpans[0].InstrumentationLibrarySpans { + spanCount += uint64(len(libSpans.Spans)) + } + dp.dataItemsGenerated.Add(spanCount) + return pdata.TracesFromOtlp(resourceSpans), false +} + +func (dp *GoldenDataProvider) GenerateMetrics() (pdata.Metrics, bool) { + if dp.metricsGenerated == nil { + var err error + dp.metricsGenerated, err = goldendataset.GenerateMetricDatas(dp.metricPairsFile) + if err != nil { + log.Printf("cannot generate metrics: %s", err) + } + } + numMetricsGenerated := len(dp.metricsGenerated) + if dp.metricsIndex == numMetricsGenerated { + return pdata.Metrics{}, true + } + pdm := dp.metricsGenerated[dp.metricsIndex] + dp.metricsIndex++ + _, dpCount := pdm.MetricAndDataPointCount() + dp.dataItemsGenerated.Add(uint64(dpCount)) + return pdm, false +} + +func (dp *GoldenDataProvider) GenerateLogs() (pdata.Logs, bool) { + return pdata.NewLogs(), true +} + +func (dp *GoldenDataProvider) GetGeneratedSpan(traceID pdata.TraceID, spanID pdata.SpanID) *otlptrace.Span { + if dp.spansMap == nil { + dp.spansMap = populateSpansMap(dp.resourceSpans) + } + key := traceIDAndSpanIDToString(traceID, spanID) + return dp.spansMap[key] +} + +func populateSpansMap(resourceSpansList []*otlptrace.ResourceSpans) map[string]*otlptrace.Span { + spansMap := make(map[string]*otlptrace.Span) + for _, resourceSpans := range resourceSpansList { + for _, libSpans := range resourceSpans.InstrumentationLibrarySpans { + for _, span := range libSpans.Spans { + key := traceIDAndSpanIDToString(pdata.TraceID(span.TraceId), pdata.SpanID(span.SpanId)) + spansMap[key] = span + } + } + } + return spansMap +} + +func traceIDAndSpanIDToString(traceID pdata.TraceID, spanID pdata.SpanID) string { + return fmt.Sprintf("%s-%s", traceID.HexString(), spanID.HexString()) +} diff --git a/internal/otel_collector/testbed/testbed/data_providers_test.go b/internal/otel_collector/testbed/testbed/data_providers_test.go new file mode 100644 index 00000000000..f80adae298c --- /dev/null +++ b/internal/otel_collector/testbed/testbed/data_providers_test.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +const metricsPictPairsFile = "../../internal/goldendataset/testdata/generated_pict_pairs_metrics.txt" + +func TestGoldenDataProvider(t *testing.T) { + dp := NewGoldenDataProvider("", "", metricsPictPairsFile, 42) + dp.SetLoadGeneratorCounters(atomic.NewUint64(0), atomic.NewUint64(0)) + var ms []pdata.Metrics + for { + m, done := dp.GenerateMetrics() + if done { + break + } + ms = append(ms, m) + } + require.Equal(t, len(dp.metricsGenerated), len(ms)) +} diff --git a/internal/otel_collector/testbed/testbed/load_generator.go b/internal/otel_collector/testbed/testbed/load_generator.go new file mode 100644 index 00000000000..0ce3175f731 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/load_generator.go @@ -0,0 +1,242 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "context" + "fmt" + "log" + "sync" + "time" + + "go.uber.org/atomic" + "golang.org/x/text/message" +) + +var printer = message.NewPrinter(message.MatchLanguage("en")) + +// LoadGenerator is a simple load generator. +type LoadGenerator struct { + sender DataSender + + dataProvider DataProvider + + // Number of batches of data items sent. + batchesSent atomic.Uint64 + + // Number of data items (spans or metric data points) sent. + dataItemsSent atomic.Uint64 + + stopOnce sync.Once + stopWait sync.WaitGroup + stopSignal chan struct{} + + options LoadOptions + + // Record information about previous errors to avoid flood of error messages. + prevErr error +} + +// LoadOptions defines the options to use for generating the load. +type LoadOptions struct { + // DataItemsPerSecond specifies how many spans, metric data points, or log + // records to generate each second. + DataItemsPerSecond int + + // ItemsPerBatch specifies how many spans, metric data points, or log + // records per batch to generate. Should be greater than zero. The number + // of batches generated per second will be DataItemsPerSecond/ItemsPerBatch. + ItemsPerBatch int + + // Attributes to add to each generated data item. Can be empty. + Attributes map[string]string + + // Parallel specifies how many goroutines to send from. + Parallel int +} + +// NewLoadGenerator creates a load generator that sends data using specified sender. +func NewLoadGenerator(dataProvider DataProvider, sender DataSender) (*LoadGenerator, error) { + if sender == nil { + return nil, fmt.Errorf("cannot create load generator without DataSender") + } + + lg := &LoadGenerator{ + stopSignal: make(chan struct{}), + sender: sender, + dataProvider: dataProvider, + } + + return lg, nil +} + +// Start the load. +func (lg *LoadGenerator) Start(options LoadOptions) { + lg.options = options + + if lg.options.ItemsPerBatch == 0 { + // 10 items per batch by default. + lg.options.ItemsPerBatch = 10 + } + + log.Printf("Starting load generator at %d items/sec.", lg.options.DataItemsPerSecond) + + // Indicate that generation is in progress. + lg.stopWait.Add(1) + + // Begin generation + go lg.generate() +} + +// Stop the load. +func (lg *LoadGenerator) Stop() { + lg.stopOnce.Do(func() { + // Signal generate() to stop. + close(lg.stopSignal) + + // Wait for it to stop. + lg.stopWait.Wait() + + // Print stats. + log.Printf("Stopped generator. %s", lg.GetStats()) + }) +} + +// GetStats returns the stats as a printable string. +func (lg *LoadGenerator) GetStats() string { + return fmt.Sprintf("Sent:%10d items", lg.DataItemsSent()) +} + +func (lg *LoadGenerator) DataItemsSent() uint64 { + return lg.dataItemsSent.Load() +} + +// IncDataItemsSent is used when a test bypasses the LoadGenerator and sends data +// directly via TestCases's Sender. This is necessary so that the total number of sent +// items in the end is correct, because the reports are printed from LoadGenerator's +// fields. This is not the best way, a better approach would be to refactor the +// reports to use their own counter and load generator and other sending sources +// to contribute to this counter. This could be done as a future improvement. +func (lg *LoadGenerator) IncDataItemsSent() { + lg.dataItemsSent.Inc() +} + +func (lg *LoadGenerator) generate() { + // Indicate that generation is done at the end + defer lg.stopWait.Done() + + if lg.options.DataItemsPerSecond == 0 { + return + } + + lg.dataProvider.SetLoadGeneratorCounters(&lg.batchesSent, &lg.dataItemsSent) + + err := lg.sender.Start() + if err != nil { + log.Printf("Cannot start sender: %v", err) + return + } + + numWorkers := 1 + + if lg.options.Parallel > 0 { + numWorkers = lg.options.Parallel + } + + var workers sync.WaitGroup + + for i := 0; i < numWorkers; i++ { + workers.Add(1) + + go func() { + defer workers.Done() + t := time.NewTicker(time.Second / time.Duration(lg.options.DataItemsPerSecond/lg.options.ItemsPerBatch/numWorkers)) + defer t.Stop() + for { + select { + case <-t.C: + switch lg.sender.(type) { + case TraceDataSender: + lg.generateTrace() + case MetricDataSender: + lg.generateMetrics() + case LogDataSender: + lg.generateLog() + default: + log.Printf("Invalid type of LoadGenerator sender") + } + case <-lg.stopSignal: + return + } + } + }() + } + + workers.Wait() + + // Send all pending generated data. + lg.sender.Flush() +} + +func (lg *LoadGenerator) generateTrace() { + traceSender := lg.sender.(TraceDataSender) + + traceData, done := lg.dataProvider.GenerateTraces() + if done { + return + } + + err := traceSender.ConsumeTraces(context.Background(), traceData) + if err == nil { + lg.prevErr = nil + } else if lg.prevErr == nil || lg.prevErr.Error() != err.Error() { + lg.prevErr = err + log.Printf("Cannot send traces: %v", err) + } +} + +func (lg *LoadGenerator) generateMetrics() { + metricSender := lg.sender.(MetricDataSender) + + metricData, done := lg.dataProvider.GenerateMetrics() + if done { + return + } + + err := metricSender.ConsumeMetrics(context.Background(), metricData) + if err == nil { + lg.prevErr = nil + } else if lg.prevErr == nil || lg.prevErr.Error() != err.Error() { + lg.prevErr = err + log.Printf("Cannot send metrics: %v", err) + } +} + +func (lg *LoadGenerator) generateLog() { + logSender := lg.sender.(LogDataSender) + + logData, done := lg.dataProvider.GenerateLogs() + if done { + return + } + + err := logSender.ConsumeLogs(context.Background(), logData) + if err == nil { + lg.prevErr = nil + } else if lg.prevErr == nil || lg.prevErr.Error() != err.Error() { + lg.prevErr = err + log.Printf("Cannot send logs: %v", err) + } +} diff --git a/internal/otel_collector/testbed/testbed/mock_backend.go b/internal/otel_collector/testbed/testbed/mock_backend.go new file mode 100644 index 00000000000..e57d74dcf71 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/mock_backend.go @@ -0,0 +1,242 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "context" + "log" + "os" + "sync" + "time" + + "go.uber.org/atomic" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// MockBackend is a backend that allows receiving the data locally. +type MockBackend struct { + // Metric and trace consumers + tc *MockTraceConsumer + mc *MockMetricConsumer + lc *MockLogConsumer + + receiver DataReceiver + + // Log file + logFilePath string + logFile *os.File + + // Start/stop flags + isStarted bool + stopOnce sync.Once + startedAt time.Time + + // Recording fields. + isRecording bool + recordMutex sync.Mutex + ReceivedTraces []pdata.Traces + ReceivedMetrics []pdata.Metrics + ReceivedLogs []pdata.Logs +} + +// NewMockBackend creates a new mock backend that receives data using specified receiver. +func NewMockBackend(logFilePath string, receiver DataReceiver) *MockBackend { + mb := &MockBackend{ + logFilePath: logFilePath, + receiver: receiver, + tc: &MockTraceConsumer{}, + mc: &MockMetricConsumer{}, + lc: &MockLogConsumer{}, + } + mb.tc.backend = mb + mb.mc.backend = mb + mb.lc.backend = mb + return mb +} + +func (mb *MockBackend) ReportFatalError(err error) { + log.Printf("Fatal error reported: %v", err) +} + +// Start a backend. +func (mb *MockBackend) Start() error { + log.Printf("Starting mock backend...") + + var err error + + // Open log file + mb.logFile, err = os.Create(mb.logFilePath) + if err != nil { + return err + } + + err = mb.receiver.Start(mb.tc, mb.mc, mb.lc) + if err != nil { + return err + } + + mb.isStarted = true + mb.startedAt = time.Now() + return nil +} + +// Stop the backend +func (mb *MockBackend) Stop() { + mb.stopOnce.Do(func() { + if !mb.isStarted { + return + } + + log.Printf("Stopping mock backend...") + + mb.logFile.Close() + mb.receiver.Stop() + + // Print stats. + log.Printf("Stopped backend. %s", mb.GetStats()) + }) +} + +// EnableRecording enables recording of all data received by MockBackend. +func (mb *MockBackend) EnableRecording() { + mb.recordMutex.Lock() + defer mb.recordMutex.Unlock() + mb.isRecording = true +} + +func (mb *MockBackend) GetStats() string { + received := mb.DataItemsReceived() + return printer.Sprintf("Received:%10d items (%d/sec)", received, int(float64(received)/time.Since(mb.startedAt).Seconds())) +} + +// DataItemsReceived returns total number of received spans and metrics. +func (mb *MockBackend) DataItemsReceived() uint64 { + return mb.tc.numSpansReceived.Load() + mb.mc.numMetricsReceived.Load() + mb.lc.numLogRecordsReceived.Load() +} + +// ClearReceivedItems clears the list of received traces and metrics. Note: counters +// return by DataItemsReceived() are not cleared, they are cumulative. +func (mb *MockBackend) ClearReceivedItems() { + mb.recordMutex.Lock() + defer mb.recordMutex.Unlock() + mb.ReceivedTraces = nil + mb.ReceivedMetrics = nil + mb.ReceivedLogs = nil +} + +func (mb *MockBackend) ConsumeTrace(td pdata.Traces) { + mb.recordMutex.Lock() + defer mb.recordMutex.Unlock() + if mb.isRecording { + mb.ReceivedTraces = append(mb.ReceivedTraces, td) + } +} + +func (mb *MockBackend) ConsumeMetric(md pdata.Metrics) { + mb.recordMutex.Lock() + defer mb.recordMutex.Unlock() + if mb.isRecording { + mb.ReceivedMetrics = append(mb.ReceivedMetrics, md) + } +} + +var _ consumer.TracesConsumer = (*MockTraceConsumer)(nil) + +func (mb *MockBackend) ConsumeLogs(ld pdata.Logs) { + mb.recordMutex.Lock() + defer mb.recordMutex.Unlock() + if mb.isRecording { + mb.ReceivedLogs = append(mb.ReceivedLogs, ld) + } +} + +type MockTraceConsumer struct { + numSpansReceived atomic.Uint64 + backend *MockBackend +} + +func (tc *MockTraceConsumer) ConsumeTraces(_ context.Context, td pdata.Traces) error { + tc.numSpansReceived.Add(uint64(td.SpanCount())) + + rs := td.ResourceSpans() + for i := 0; i < rs.Len(); i++ { + ils := rs.At(i).InstrumentationLibrarySpans() + for j := 0; j < ils.Len(); j++ { + spans := ils.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + span := spans.At(k) + var spanSeqnum int64 + var traceSeqnum int64 + + seqnumAttr, ok := span.Attributes().Get("load_generator.span_seq_num") + if ok { + spanSeqnum = seqnumAttr.IntVal() + } + + seqnumAttr, ok = span.Attributes().Get("load_generator.trace_seq_num") + if ok { + traceSeqnum = seqnumAttr.IntVal() + } + + // Ignore the seqnums for now. We will use them later. + _ = spanSeqnum + _ = traceSeqnum + + } + } + } + + tc.backend.ConsumeTrace(td) + + return nil +} + +var _ consumer.MetricsConsumer = (*MockMetricConsumer)(nil) + +type MockMetricConsumer struct { + numMetricsReceived atomic.Uint64 + backend *MockBackend +} + +func (mc *MockMetricConsumer) ConsumeMetrics(_ context.Context, md pdata.Metrics) error { + _, dataPoints := md.MetricAndDataPointCount() + mc.numMetricsReceived.Add(uint64(dataPoints)) + mc.backend.ConsumeMetric(md) + return nil +} + +func (tc *MockTraceConsumer) MockConsumeTraceData(spansCount int) error { + tc.numSpansReceived.Add(uint64(spansCount)) + return nil +} + +func (mc *MockMetricConsumer) MockConsumeMetricData(metricsCount int) error { + mc.numMetricsReceived.Add(uint64(metricsCount)) + return nil +} + +type MockLogConsumer struct { + numLogRecordsReceived atomic.Uint64 + backend *MockBackend +} + +func (mc *MockLogConsumer) ConsumeLogs(_ context.Context, ld pdata.Logs) error { + recordCount := ld.LogRecordCount() + mc.numLogRecordsReceived.Add(uint64(recordCount)) + mc.backend.ConsumeLogs(ld) + return nil +} diff --git a/internal/otel_collector/testbed/testbed/mock_backend_test.go b/internal/otel_collector/testbed/testbed/mock_backend_test.go new file mode 100644 index 00000000000..a837120ef95 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/mock_backend_test.go @@ -0,0 +1,101 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGeneratorAndBackend(t *testing.T) { + port := GetAvailablePort(t) + + tests := []struct { + name string + receiver DataReceiver + sender DataSender + }{ + { + name: "Jaeger-JaegerGRPC", + receiver: NewJaegerDataReceiver(port), + sender: NewJaegerGRPCDataSender(DefaultHost, port), + }, + { + name: "Zipkin-Zipkin", + receiver: NewZipkinDataReceiver(port), + sender: NewZipkinDataSender(DefaultHost, port), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mb := NewMockBackend("mockbackend.log", test.receiver) + + assert.EqualValues(t, 0, mb.DataItemsReceived()) + require.NoError(t, mb.Start(), "Cannot start backend") + + defer mb.Stop() + + options := LoadOptions{DataItemsPerSecond: 10_000, ItemsPerBatch: 10} + dataProvider := NewPerfTestDataProvider(options) + lg, err := NewLoadGenerator(dataProvider, test.sender) + require.NoError(t, err, "Cannot start load generator") + + assert.EqualValues(t, 0, lg.dataItemsSent.Load()) + + // Generate at 1000 SPS + lg.Start(LoadOptions{DataItemsPerSecond: 1000}) + + // Wait until at least 50 spans are sent + WaitFor(t, func() bool { return lg.DataItemsSent() > 50 }, "DataItemsSent > 50") + + lg.Stop() + + // The backend should receive everything generated. + assert.Equal(t, lg.DataItemsSent(), mb.DataItemsReceived()) + }) + } +} + +// WaitFor the specific condition for up to 10 seconds. Records a test error +// if condition does not become true. +func WaitFor(t *testing.T, cond func() bool, errMsg ...interface{}) bool { + startTime := time.Now() + + // Start with 5 ms waiting interval between condition re-evaluation. + waitInterval := time.Millisecond * 5 + + for { + time.Sleep(waitInterval) + + // Increase waiting interval exponentially up to 500 ms. + if waitInterval < time.Millisecond*500 { + waitInterval *= 2 + } + + if cond() { + return true + } + + if time.Since(startTime) > time.Second*10 { + // Waited too long + t.Error("Time out waiting for", errMsg) + return false + } + } +} diff --git a/internal/otel_collector/testbed/testbed/mockconsumer.go b/internal/otel_collector/testbed/testbed/mockconsumer.go new file mode 100644 index 00000000000..7d22ccbe981 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/mockconsumer.go @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +// MockDataConsumer is an interface that keeps the count of number of events received by mock receiver. +// This is mainly useful for the Exporters that are not have the matching receiver +type MockTraceDataConsumer interface { + // MockConsumeTraceData receives traces and counts the number of events received. + MockConsumeTraceData(spansCount int) error +} + +type MockMetricDataConsumer interface { + // MockConsumeMetricData receives metrics and counts the number of events received. + MockConsumeMetricData(metricsCount int) error +} diff --git a/internal/otel_collector/testbed/testbed/options.go b/internal/otel_collector/testbed/testbed/options.go new file mode 100644 index 00000000000..ed401666c8f --- /dev/null +++ b/internal/otel_collector/testbed/testbed/options.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tests contains test cases. To run the tests go to tests directory and run: +// RUN_TESTBED=1 go test -v + +package testbed + +// TestCaseOption defines a TestCase option. +type TestCaseOption struct { + option func(t *TestCase) +} + +// Apply takes a TestCase and runs the option function on it. +func (o TestCaseOption) Apply(t *TestCase) { + o.option(t) +} + +// WithSkipResults option disables writing out results file for a TestCase. +func WithSkipResults() TestCaseOption { + return TestCaseOption{func(t *TestCase) { + t.skipResults = true + }} +} + +// WithConfigFile allows a custom configuration file for TestCase. +func WithConfigFile(file string) TestCaseOption { + return TestCaseOption{func(t *TestCase) { + t.agentConfigFile = file + }} +} diff --git a/internal/otel_collector/testbed/testbed/otelcol_runner.go b/internal/otel_collector/testbed/testbed/otelcol_runner.go new file mode 100644 index 00000000000..489fff4bc4e --- /dev/null +++ b/internal/otel_collector/testbed/testbed/otelcol_runner.go @@ -0,0 +1,178 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "fmt" + "strings" + + "github.com/shirou/gopsutil/process" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/internal/version" + "go.opentelemetry.io/collector/service" +) + +// OtelcolRunner defines the interface for configuring, starting and stopping one or more instances of +// otelcol which will be the subject of testing being executed. +type OtelcolRunner interface { + // PrepareConfig stores the provided YAML-based otelcol configuration file in the format needed by the otelcol + // instance(s) this runner manages. If successful, it returns the cleanup config function to be executed after + // the test is executed. + PrepareConfig(configStr string) (configCleanup func(), err error) + // Starts the otelcol instance(s) if not already running which is the subject of the test to be run. + // It returns the host:port of the data receiver to post test data to. + Start(args StartParams) error + // Stops the otelcol instance(s) which are the subject of the test just run if applicable. Returns whether + // the instance was actually stopped or not. + Stop() (stopped bool, err error) + // WatchResourceConsumption toggles on the monitoring of resource consumpution by the otelcol instance under test. + WatchResourceConsumption() error + // GetProcessMon returns the Process being used to monitor resource consumption. + GetProcessMon() *process.Process + // GetTotalConsumption returns the data collected by the process monitor. + GetTotalConsumption() *ResourceConsumption + // GetResourceConsumption returns the data collected by the process monitor as a display string. + GetResourceConsumption() string +} + +// InProcessCollector implements the OtelcolRunner interfaces running a single otelcol as a go routine within the +// same process as the test executor. +type InProcessCollector struct { + logger *zap.Logger + factories component.Factories + config *configmodels.Config + svc *service.Application + appDone chan struct{} + stopped bool +} + +// NewInProcessCollector crewtes a new InProcessCollector using the supplied component factories. +func NewInProcessCollector(factories component.Factories) *InProcessCollector { + return &InProcessCollector{ + factories: factories, + } +} + +func (ipp *InProcessCollector) PrepareConfig(configStr string) (configCleanup func(), err error) { + configCleanup = func() { + // NoOp + } + var logger *zap.Logger + logger, err = configureLogger() + if err != nil { + return configCleanup, err + } + ipp.logger = logger + v := config.NewViper() + v.SetConfigType("yaml") + v.ReadConfig(strings.NewReader(configStr)) + cfg, err := config.Load(v, ipp.factories) + if err != nil { + return configCleanup, err + } + err = config.ValidateConfig(cfg, zap.NewNop()) + if err != nil { + return configCleanup, err + } + ipp.config = cfg + return configCleanup, err +} + +func (ipp *InProcessCollector) Start(args StartParams) error { + params := service.Parameters{ + ApplicationStartInfo: component.ApplicationStartInfo{ + ExeName: "otelcol", + LongName: "InProcess Collector", + Version: version.Version, + GitHash: version.GitHash, + }, + ConfigFactory: func(_ *viper.Viper, _ *cobra.Command, _ component.Factories) (*configmodels.Config, error) { + return ipp.config, nil + }, + Factories: ipp.factories, + } + var err error + ipp.svc, err = service.New(params) + if err != nil { + return err + } + ipp.svc.Command().SetArgs(args.CmdArgs) + + ipp.appDone = make(chan struct{}) + go func() { + defer close(ipp.appDone) + appErr := ipp.svc.Run() + if appErr != nil { + err = appErr + } + }() + + for state := range ipp.svc.GetStateChannel() { + switch state { + case service.Starting: + // NoOp + case service.Running: + return err + default: + err = fmt.Errorf("unable to start, otelcol state is %d", state) + } + } + return err +} + +func (ipp *InProcessCollector) Stop() (stopped bool, err error) { + if !ipp.stopped { + ipp.stopped = true + ipp.svc.Shutdown() + } + <-ipp.appDone + stopped = ipp.stopped + return stopped, err +} + +func (ipp *InProcessCollector) WatchResourceConsumption() error { + return nil +} + +func (ipp *InProcessCollector) GetProcessMon() *process.Process { + return nil +} + +func (ipp *InProcessCollector) GetTotalConsumption() *ResourceConsumption { + return &ResourceConsumption{ + CPUPercentAvg: 0, + CPUPercentMax: 0, + RAMMiBAvg: 0, + RAMMiBMax: 0, + } +} + +func (ipp *InProcessCollector) GetResourceConsumption() string { + return "" +} + +func configureLogger() (*zap.Logger, error) { + conf := zap.NewDevelopmentConfig() + conf.Level.SetLevel(zapcore.InfoLevel) + logger, err := conf.Build() + return logger, err +} diff --git a/internal/otel_collector/testbed/testbed/otelcol_runner_test.go b/internal/otel_collector/testbed/testbed/otelcol_runner_test.go new file mode 100644 index 00000000000..2c022b18cb3 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/otelcol_runner_test.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/service/defaultcomponents" +) + +func TestNewInProcessPipeline(t *testing.T) { + factories, err := defaultcomponents.Components() + assert.NoError(t, err) + sender := NewOTLPTraceDataSender(DefaultHost, GetAvailablePort(t)) + receiver := NewOTLPDataReceiver(DefaultOTLPPort) + runner := NewInProcessCollector(factories) + + format := ` +receivers:%v +exporters:%v +processors: + batch: + +extensions: + +service: + extensions: + pipelines: + traces: + receivers: [%v] + processors: [batch] + exporters: [%v] +` + config := fmt.Sprintf( + format, + sender.GenConfigYAMLStr(), + receiver.GenConfigYAMLStr(), + sender.ProtocolName(), + receiver.ProtocolName(), + ) + configCleanup, cfgErr := runner.PrepareConfig(config) + defer configCleanup() + assert.NoError(t, cfgErr) + assert.NotNil(t, configCleanup) + assert.NotNil(t, runner.config) + args := StartParams{} + defer runner.Stop() + assert.NoError(t, runner.Start(args)) + assert.NotNil(t, runner.svc) +} diff --git a/internal/otel_collector/testbed/testbed/receivers.go b/internal/otel_collector/testbed/testbed/receivers.go new file mode 100644 index 00000000000..52aebc0508d --- /dev/null +++ b/internal/otel_collector/testbed/testbed/receivers.go @@ -0,0 +1,383 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/config" + "github.com/prometheus/prometheus/discovery" + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver/jaegerreceiver" + "go.opentelemetry.io/collector/receiver/opencensusreceiver" + "go.opentelemetry.io/collector/receiver/otlpreceiver" + "go.opentelemetry.io/collector/receiver/prometheusreceiver" + "go.opentelemetry.io/collector/receiver/zipkinreceiver" +) + +// DataReceiver allows to receive traces or metrics. This is an interface that must +// be implemented by all protocols that want to be used in MockBackend. +// Note the terminology: testbed.DataReceiver is something that can listen and receive data +// from Collector and the corresponding entity in the Collector that sends this data is +// an exporter. +type DataReceiver interface { + Start(tc consumer.TracesConsumer, mc consumer.MetricsConsumer, lc consumer.LogsConsumer) error + Stop() error + + // Generate a config string to place in exporter part of collector config + // so that it can send data to this receiver. + GenConfigYAMLStr() string + + // Return exporterType name to use in collector config pipeline. + ProtocolName() string +} + +// DataReceiverBase implement basic functions needed by all receivers. +type DataReceiverBase struct { + // Port on which to listen. + Port int +} + +const DefaultHost = "localhost" + +func (mb *DataReceiverBase) ReportFatalError(err error) { + log.Printf("Fatal error reported: %v", err) +} + +// GetFactory of the specified kind. Returns the factory for a component type. +func (mb *DataReceiverBase) GetFactory(_ component.Kind, _ configmodels.Type) component.Factory { + return nil +} + +// Return map of extensions. Only enabled and created extensions will be returned. +func (mb *DataReceiverBase) GetExtensions() map[configmodels.Extension]component.ServiceExtension { + return nil +} + +func (mb *DataReceiverBase) GetExporters() map[configmodels.DataType]map[configmodels.Exporter]component.Exporter { + return nil +} + +// OCDataReceiver implements OpenCensus format receiver. +type OCDataReceiver struct { + DataReceiverBase + traceReceiver component.TracesReceiver + metricsReceiver component.MetricsReceiver +} + +// Ensure OCDataReceiver implements DataReceiver. +var _ DataReceiver = (*OCDataReceiver)(nil) + +const DefaultOCPort = 56565 + +// NewOCDataReceiver creates a new OCDataReceiver that will listen on the specified port after Start +// is called. +func NewOCDataReceiver(port int) *OCDataReceiver { + return &OCDataReceiver{DataReceiverBase: DataReceiverBase{Port: port}} +} + +func (or *OCDataReceiver) Start(tc consumer.TracesConsumer, mc consumer.MetricsConsumer, _ consumer.LogsConsumer) error { + factory := opencensusreceiver.NewFactory() + cfg := factory.CreateDefaultConfig().(*opencensusreceiver.Config) + cfg.SetName(or.ProtocolName()) + cfg.NetAddr = confignet.NetAddr{Endpoint: fmt.Sprintf("localhost:%d", or.Port), Transport: "tcp"} + var err error + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + if or.traceReceiver, err = factory.CreateTracesReceiver(context.Background(), params, cfg, tc); err != nil { + return err + } + if or.metricsReceiver, err = factory.CreateMetricsReceiver(context.Background(), params, cfg, mc); err != nil { + return err + } + if err = or.traceReceiver.Start(context.Background(), or); err != nil { + return err + } + return or.metricsReceiver.Start(context.Background(), or) +} + +func (or *OCDataReceiver) Stop() error { + if err := or.traceReceiver.Shutdown(context.Background()); err != nil { + return err + } + if err := or.metricsReceiver.Shutdown(context.Background()); err != nil { + return err + } + return nil +} + +func (or *OCDataReceiver) GenConfigYAMLStr() string { + // Note that this generates an exporter config for agent. + return fmt.Sprintf(` + opencensus: + endpoint: "localhost:%d" + insecure: true`, or.Port) +} + +func (or *OCDataReceiver) ProtocolName() string { + return "opencensus" +} + +// JaegerDataReceiver implements Jaeger format receiver. +type JaegerDataReceiver struct { + DataReceiverBase + receiver component.TracesReceiver +} + +var _ DataReceiver = (*JaegerDataReceiver)(nil) + +const DefaultJaegerPort = 14250 + +func NewJaegerDataReceiver(port int) *JaegerDataReceiver { + return &JaegerDataReceiver{DataReceiverBase: DataReceiverBase{Port: port}} +} + +func (jr *JaegerDataReceiver) Start(tc consumer.TracesConsumer, _ consumer.MetricsConsumer, _ consumer.LogsConsumer) error { + factory := jaegerreceiver.NewFactory() + cfg := factory.CreateDefaultConfig().(*jaegerreceiver.Config) + cfg.SetName(jr.ProtocolName()) + cfg.Protocols.GRPC = &configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{Endpoint: fmt.Sprintf("localhost:%d", jr.Port), Transport: "tcp"}, + } + var err error + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + jr.receiver, err = factory.CreateTracesReceiver(context.Background(), params, cfg, tc) + if err != nil { + return err + } + + return jr.receiver.Start(context.Background(), jr) +} + +func (jr *JaegerDataReceiver) Stop() error { + return jr.receiver.Shutdown(context.Background()) +} + +func (jr *JaegerDataReceiver) GenConfigYAMLStr() string { + // Note that this generates an exporter config for agent. + return fmt.Sprintf(` + jaeger: + endpoint: "localhost:%d" + insecure: true`, jr.Port) +} + +func (jr *JaegerDataReceiver) ProtocolName() string { + return "jaeger" +} + +// baseOTLPDataReceiver implements the OTLP format receiver. +type baseOTLPDataReceiver struct { + DataReceiverBase + // One of the "otlp" for OTLP over gRPC or "otlphttp" for OTLP over HTTP. + exporterType string + traceReceiver component.TracesReceiver + metricsReceiver component.MetricsReceiver + logReceiver component.LogsReceiver +} + +func (bor *baseOTLPDataReceiver) Start(tc consumer.TracesConsumer, mc consumer.MetricsConsumer, lc consumer.LogsConsumer) error { + factory := otlpreceiver.NewFactory() + cfg := factory.CreateDefaultConfig().(*otlpreceiver.Config) + cfg.SetName(bor.exporterType) + if bor.exporterType == "otlp" { + cfg.GRPC.NetAddr = confignet.NetAddr{Endpoint: fmt.Sprintf("localhost:%d", bor.Port), Transport: "tcp"} + cfg.HTTP = nil + } else { + cfg.HTTP.Endpoint = fmt.Sprintf("localhost:%d", bor.Port) + cfg.GRPC = nil + } + var err error + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + if bor.traceReceiver, err = factory.CreateTracesReceiver(context.Background(), params, cfg, tc); err != nil { + return err + } + if bor.metricsReceiver, err = factory.CreateMetricsReceiver(context.Background(), params, cfg, mc); err != nil { + return err + } + if bor.logReceiver, err = factory.CreateLogsReceiver(context.Background(), params, cfg, lc); err != nil { + return err + } + + if err = bor.traceReceiver.Start(context.Background(), bor); err != nil { + return err + } + if err = bor.metricsReceiver.Start(context.Background(), bor); err != nil { + return err + } + return bor.logReceiver.Start(context.Background(), bor) +} + +func (bor *baseOTLPDataReceiver) Stop() error { + if err := bor.traceReceiver.Shutdown(context.Background()); err != nil { + return err + } + if err := bor.metricsReceiver.Shutdown(context.Background()); err != nil { + return err + } + return bor.logReceiver.Shutdown(context.Background()) +} + +func (bor *baseOTLPDataReceiver) ProtocolName() string { + return bor.exporterType +} + +func (bor *baseOTLPDataReceiver) GenConfigYAMLStr() string { + addr := fmt.Sprintf("localhost:%d", bor.Port) + if bor.exporterType == "otlphttp" { + addr = "http://" + addr + } + // Note that this generates an exporter config for agent. + return fmt.Sprintf(` + %s: + endpoint: "%s" + insecure: true`, bor.exporterType, addr) +} + +const DefaultOTLPPort = 55680 + +// NewOTLPDataReceiver creates a new OTLP DataReceiver that will listen on the specified port after Start +// is called. +func NewOTLPDataReceiver(port int) DataReceiver { + return &baseOTLPDataReceiver{ + DataReceiverBase: DataReceiverBase{Port: port}, + exporterType: "otlp", + } +} + +// NewOTLPDataReceiver creates a new OTLP/HTTP DataReceiver that will listen on the specified port after Start +// is called. +func NewOTLPHTTPDataReceiver(port int) DataReceiver { + return &baseOTLPDataReceiver{ + DataReceiverBase: DataReceiverBase{Port: port}, + exporterType: "otlphttp", + } +} + +// ZipkinDataReceiver implements Zipkin format receiver. +type ZipkinDataReceiver struct { + DataReceiverBase + receiver component.TracesReceiver +} + +var _ DataReceiver = (*ZipkinDataReceiver)(nil) + +const DefaultZipkinAddressPort = 9411 + +func NewZipkinDataReceiver(port int) *ZipkinDataReceiver { + return &ZipkinDataReceiver{DataReceiverBase: DataReceiverBase{Port: port}} +} + +func (zr *ZipkinDataReceiver) Start(tc consumer.TracesConsumer, _ consumer.MetricsConsumer, _ consumer.LogsConsumer) error { + factory := zipkinreceiver.NewFactory() + cfg := factory.CreateDefaultConfig().(*zipkinreceiver.Config) + cfg.SetName(zr.ProtocolName()) + cfg.Endpoint = fmt.Sprintf("localhost:%d", zr.Port) + + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + var err error + zr.receiver, err = factory.CreateTracesReceiver(context.Background(), params, cfg, tc) + + if err != nil { + return err + } + + return zr.receiver.Start(context.Background(), zr) +} + +func (zr *ZipkinDataReceiver) Stop() error { + return zr.receiver.Shutdown(context.Background()) +} + +func (zr *ZipkinDataReceiver) GenConfigYAMLStr() string { + // Note that this generates an exporter config for agent. + return fmt.Sprintf(` + zipkin: + endpoint: http://localhost:%d/api/v2/spans + format: json`, zr.Port) +} + +func (zr *ZipkinDataReceiver) ProtocolName() string { + return "zipkin" +} + +// prometheus + +type PrometheusDataReceiver struct { + DataReceiverBase + receiver component.MetricsReceiver +} + +var _ DataReceiver = (*PrometheusDataReceiver)(nil) + +func NewPrometheusDataReceiver(port int) *PrometheusDataReceiver { + return &PrometheusDataReceiver{DataReceiverBase: DataReceiverBase{Port: port}} +} + +func (dr *PrometheusDataReceiver) Start(_ consumer.TracesConsumer, mc consumer.MetricsConsumer, _ consumer.LogsConsumer) error { + factory := prometheusreceiver.NewFactory() + cfg := factory.CreateDefaultConfig().(*prometheusreceiver.Config) + addr := fmt.Sprintf("0.0.0.0:%d", dr.Port) + cfg.PrometheusConfig = &config.Config{ + ScrapeConfigs: []*config.ScrapeConfig{{ + JobName: "testbed-job", + ScrapeInterval: model.Duration(100 * time.Millisecond), + ScrapeTimeout: model.Duration(time.Second), + ServiceDiscoveryConfigs: discovery.Configs{ + &discovery.StaticConfig{ + { + Targets: []model.LabelSet{{ + "__address__": model.LabelValue(addr), + "__scheme__": "http", + "__metrics_path__": "/metrics", + }}, + }, + }, + }, + }}, + } + var err error + params := component.ReceiverCreateParams{Logger: zap.NewNop()} + dr.receiver, err = factory.CreateMetricsReceiver(context.Background(), params, cfg, mc) + if err != nil { + return err + } + return dr.receiver.Start(context.Background(), dr) +} + +func (dr *PrometheusDataReceiver) Stop() error { + return dr.receiver.Shutdown(context.Background()) +} + +// Generate exporter yaml +func (dr *PrometheusDataReceiver) GenConfigYAMLStr() string { + format := ` + prometheus: + endpoint: "localhost:%d" +` + return fmt.Sprintf(format, dr.Port) +} + +func (dr *PrometheusDataReceiver) ProtocolName() string { + return "prometheus" +} diff --git a/internal/otel_collector/testbed/testbed/results.go b/internal/otel_collector/testbed/testbed/results.go new file mode 100644 index 00000000000..809897844b6 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/results.go @@ -0,0 +1,215 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "fmt" + "io" + "log" + "os" + "path" + "time" +) + +// TestResultsSummary defines the interface to record results of one category of testing. +type TestResultsSummary interface { + // Create and open the file and write headers. + Init(resultsDir string) + // Add results for one test. + Add(testName string, result interface{}) + // Save the total results and close the file. + Save() +} + +// PerformanceResults implements the TestResultsSummary interface with fields suitable for reporting +// performance test results. +type PerformanceResults struct { + resultsDir string + resultsFile *os.File + perTestResults []*PerformanceTestResult + totalDuration time.Duration +} + +// PerformanceTestResult reports the results of a single performance test. +type PerformanceTestResult struct { + testName string + result string + duration time.Duration + cpuPercentageAvg float64 + cpuPercentageMax float64 + ramMibAvg uint32 + ramMibMax uint32 + sentSpanCount uint64 + receivedSpanCount uint64 + errorCause string +} + +func (r *PerformanceResults) Init(resultsDir string) { + r.resultsDir = resultsDir + r.perTestResults = []*PerformanceTestResult{} + + // Create resultsSummary file + os.MkdirAll(resultsDir, os.FileMode(0755)) + var err error + r.resultsFile, err = os.Create(path.Join(r.resultsDir, "TESTRESULTS.md")) + if err != nil { + log.Fatalf(err.Error()) + } + + // Write the header + _, _ = io.WriteString(r.resultsFile, + "# Test PerformanceResults\n"+ + fmt.Sprintf("Started: %s\n\n", time.Now().Format(time.RFC1123Z))+ + "Test |Result|Duration|CPU Avg%|CPU Max%|RAM Avg MiB|RAM Max MiB|Sent Items|Received Items|\n"+ + "----------------------------------------|------|-------:|-------:|-------:|----------:|----------:|---------:|-------------:|\n") +} + +// Save the total results and close the file. +func (r *PerformanceResults) Save() { + _, _ = io.WriteString(r.resultsFile, + fmt.Sprintf("\nTotal duration: %.0fs\n", r.totalDuration.Seconds())) + r.resultsFile.Close() +} + +// Add results for one test. +func (r *PerformanceResults) Add(_ string, result interface{}) { + testResult, ok := result.(*PerformanceTestResult) + if !ok { + return + } + _, _ = io.WriteString(r.resultsFile, + fmt.Sprintf("%-40s|%-6s|%7.0fs|%8.1f|%8.1f|%11d|%11d|%10d|%14d|%s\n", + testResult.testName, + testResult.result, + testResult.duration.Seconds(), + testResult.cpuPercentageAvg, + testResult.cpuPercentageMax, + testResult.ramMibAvg, + testResult.ramMibMax, + testResult.sentSpanCount, + testResult.receivedSpanCount, + testResult.errorCause, + ), + ) + r.totalDuration += testResult.duration +} + +// CorrectnessResults implements the TestResultsSummary interface with fields suitable for reporting data translation +// correctness test results. +type CorrectnessResults struct { + resultsDir string + resultsFile *os.File + perTestResults []*CorrectnessTestResult + totalAssertionFailures uint64 + totalDuration time.Duration +} + +// CorrectnessTestResult reports the results of a single correctness test. +type CorrectnessTestResult struct { + testName string + result string + duration time.Duration + sentSpanCount uint64 + receivedSpanCount uint64 + traceAssertionFailureCount uint64 + traceAssertionFailures []*TraceAssertionFailure +} + +type TraceAssertionFailure struct { + typeName string + dataComboName string + fieldPath string + expectedValue interface{} + actualValue interface{} + sumCount int +} + +func (af TraceAssertionFailure) String() string { + return fmt.Sprintf("%s/%s e=%#v a=%#v ", af.dataComboName, af.fieldPath, af.expectedValue, af.actualValue) +} + +func (r *CorrectnessResults) Init(resultsDir string) { + r.resultsDir = resultsDir + r.perTestResults = []*CorrectnessTestResult{} + + // Create resultsSummary file + os.MkdirAll(resultsDir, os.FileMode(0755)) + var err error + r.resultsFile, err = os.Create(path.Join(r.resultsDir, "CORRECTNESSRESULTS.md")) + if err != nil { + log.Fatalf(err.Error()) + } + + // Write the header + _, _ = io.WriteString(r.resultsFile, + "# Test Results\n"+ + fmt.Sprintf("Started: %s\n\n", time.Now().Format(time.RFC1123Z))+ + "Test |Result|Duration|Sent Items|Received Items|Failure Count|Failures\n"+ + "----------------------------------------|------|-------:|---------:|-------------:|------------:|--------\n") +} + +func (r *CorrectnessResults) Add(_ string, result interface{}) { + testResult, ok := result.(*CorrectnessTestResult) + if !ok { + return + } + consolidated := consolidateAssertionFailures(testResult.traceAssertionFailures) + failuresStr := "" + for _, af := range consolidated { + failuresStr = fmt.Sprintf("%s%s,%#v!=%#v,count=%d; ", failuresStr, af.fieldPath, af.expectedValue, + af.actualValue, af.sumCount) + } + _, _ = io.WriteString(r.resultsFile, + fmt.Sprintf("%-40s|%-6s|%7.0fs|%10d|%14d|%13d|%s\n", + testResult.testName, + testResult.result, + testResult.duration.Seconds(), + testResult.sentSpanCount, + testResult.receivedSpanCount, + testResult.traceAssertionFailureCount, + failuresStr, + ), + ) + r.perTestResults = append(r.perTestResults, testResult) + r.totalAssertionFailures += testResult.traceAssertionFailureCount + r.totalDuration += testResult.duration +} + +func (r *CorrectnessResults) Save() { + _, _ = io.WriteString(r.resultsFile, + fmt.Sprintf("\nTotal assertion failures: %d\n", r.totalAssertionFailures)) + _, _ = io.WriteString(r.resultsFile, + fmt.Sprintf("\nTotal duration: %.0fs\n", r.totalDuration.Seconds())) + r.resultsFile.Close() +} + +func consolidateAssertionFailures(failures []*TraceAssertionFailure) map[string]*TraceAssertionFailure { + afMap := make(map[string]*TraceAssertionFailure) + for _, f := range failures { + summary := afMap[f.fieldPath] + if summary == nil { + summary = &TraceAssertionFailure{ + typeName: f.typeName, + dataComboName: f.dataComboName + "...", + fieldPath: f.fieldPath, + expectedValue: f.expectedValue, + actualValue: f.actualValue, + } + afMap[f.fieldPath] = summary + } + summary.sumCount++ + } + return afMap +} diff --git a/internal/otel_collector/testbed/testbed/senders.go b/internal/otel_collector/testbed/testbed/senders.go new file mode 100644 index 00000000000..3e00b497d30 --- /dev/null +++ b/internal/otel_collector/testbed/testbed/senders.go @@ -0,0 +1,746 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "strconv" + "time" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/exporter/jaegerexporter" + "go.opentelemetry.io/collector/exporter/opencensusexporter" + "go.opentelemetry.io/collector/exporter/otlpexporter" + "go.opentelemetry.io/collector/exporter/otlphttpexporter" + "go.opentelemetry.io/collector/exporter/prometheusexporter" + "go.opentelemetry.io/collector/exporter/zipkinexporter" +) + +// DataSender defines the interface that allows sending data. This is an interface +// that must be implemented by all protocols that want to be used in LoadGenerator. +// Note the terminology: testbed.DataSender is something that sends data to Collector +// and the corresponding entity that receives the data in the Collector is a receiver. +type DataSender interface { + // Start sender and connect to the configured endpoint. Must be called before + // sending data. + Start() error + + // Send any accumulated data. + Flush() + + // Return the port to which this sender will send data. + GetEndpoint() string + + // Generate a config string to place in receiver part of collector config + // so that it can receive data from this sender. + GenConfigYAMLStr() string + + // Return exporterType name to use in collector config pipeline. + ProtocolName() string +} + +// TraceDataSender defines the interface that allows sending trace data. It adds ability +// to send a batch of Spans to the DataSender interface. +type TraceDataSender interface { + DataSender + consumer.TracesConsumer +} + +// MetricDataSender defines the interface that allows sending metric data. It adds ability +// to send a batch of Metrics to the DataSender interface. +type MetricDataSender interface { + DataSender + consumer.MetricsConsumer +} + +// LogDataSender defines the interface that allows sending log data. It adds ability +// to send a batch of Logs to the DataSender interface. +type LogDataSender interface { + DataSender + consumer.LogsConsumer +} + +type DataSenderBase struct { + Port int + Host string +} + +func (dsb *DataSenderBase) GetEndpoint() string { + return fmt.Sprintf("%s:%d", dsb.Host, dsb.Port) +} + +func (dsb *DataSenderBase) ReportFatalError(err error) { + log.Printf("Fatal error reported: %v", err) +} + +// GetFactory of the specified kind. Returns the factory for a component type. +func (dsb *DataSenderBase) GetFactory(_ component.Kind, _ configmodels.Type) component.Factory { + return nil +} + +// Return map of extensions. Only enabled and created extensions will be returned. +func (dsb *DataSenderBase) GetExtensions() map[configmodels.Extension]component.ServiceExtension { + return nil +} + +func (dsb *DataSenderBase) GetExporters() map[configmodels.DataType]map[configmodels.Exporter]component.Exporter { + return nil +} + +func (dsb *DataSenderBase) Flush() { + // Exporter interface does not support Flush, so nothing to do. +} + +// JaegerGRPCDataSender implements TraceDataSender for Jaeger thrift_http exporterType. +type JaegerGRPCDataSender struct { + DataSenderBase + consumer.TracesConsumer +} + +// Ensure JaegerGRPCDataSender implements TraceDataSender. +var _ TraceDataSender = (*JaegerGRPCDataSender)(nil) + +// NewJaegerGRPCDataSender creates a new Jaeger exporterType sender that will send +// to the specified port after Start is called. +func NewJaegerGRPCDataSender(host string, port int) *JaegerGRPCDataSender { + return &JaegerGRPCDataSender{ + DataSenderBase: DataSenderBase{Port: port, Host: host}, + } +} + +func (je *JaegerGRPCDataSender) Start() error { + factory := jaegerexporter.NewFactory() + cfg := factory.CreateDefaultConfig().(*jaegerexporter.Config) + // Disable retries, we should push data and if error just log it. + cfg.RetrySettings.Enabled = false + // Disable sending queue, we should push data from the caller goroutine. + cfg.QueueSettings.Enabled = false + cfg.Endpoint = je.GetEndpoint() + cfg.TLSSetting = configtls.TLSClientSetting{ + Insecure: true, + } + + exp, err := factory.CreateTracesExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + je.TracesConsumer = exp + return exp.Start(context.Background(), je) +} + +func (je *JaegerGRPCDataSender) GenConfigYAMLStr() string { + return fmt.Sprintf(` + jaeger: + protocols: + grpc: + endpoint: "%s"`, je.GetEndpoint()) +} + +func (je *JaegerGRPCDataSender) ProtocolName() string { + return "jaeger" +} + +type ocDataSender struct { + DataSenderBase +} + +func (ods *ocDataSender) fillConfig(cfg *opencensusexporter.Config) *opencensusexporter.Config { + cfg.Endpoint = ods.GetEndpoint() + cfg.TLSSetting = configtls.TLSClientSetting{ + Insecure: true, + } + return cfg +} + +func (ods *ocDataSender) GenConfigYAMLStr() string { + // Note that this generates a receiver config for agent. + return fmt.Sprintf(` + opencensus: + endpoint: "%s"`, ods.GetEndpoint()) +} + +func (ods *ocDataSender) ProtocolName() string { + return "opencensus" +} + +// OCTraceDataSender implements TraceDataSender for OpenCensus trace exporterType. +type OCTraceDataSender struct { + ocDataSender + consumer.TracesConsumer +} + +// Ensure OCTraceDataSender implements TraceDataSender. +var _ TraceDataSender = (*OCTraceDataSender)(nil) + +// NewOCTraceDataSender creates a new OCTraceDataSender that will send +// to the specified port after Start is called. +func NewOCTraceDataSender(host string, port int) *OCTraceDataSender { + return &OCTraceDataSender{ + ocDataSender: ocDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (ote *OCTraceDataSender) Start() error { + factory := opencensusexporter.NewFactory() + cfg := ote.fillConfig(factory.CreateDefaultConfig().(*opencensusexporter.Config)) + exp, err := factory.CreateTracesExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + ote.TracesConsumer = exp + return exp.Start(context.Background(), ote) +} + +// OCMetricsDataSender implements MetricDataSender for OpenCensus metrics exporterType. +type OCMetricsDataSender struct { + ocDataSender + consumer.MetricsConsumer +} + +// Ensure OCMetricsDataSender implements MetricDataSender. +var _ MetricDataSender = (*OCMetricsDataSender)(nil) + +// NewOCMetricDataSender creates a new OpenCensus metric exporterType sender that will send +// to the specified port after Start is called. +func NewOCMetricDataSender(host string, port int) *OCMetricsDataSender { + return &OCMetricsDataSender{ + ocDataSender: ocDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (ome *OCMetricsDataSender) Start() error { + factory := opencensusexporter.NewFactory() + cfg := ome.fillConfig(factory.CreateDefaultConfig().(*opencensusexporter.Config)) + exp, err := factory.CreateMetricsExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + ome.MetricsConsumer = exp + return exp.Start(context.Background(), ome) +} + +type otlpHTTPDataSender struct { + DataSenderBase +} + +func (ods *otlpHTTPDataSender) fillConfig(cfg *otlphttpexporter.Config) *otlphttpexporter.Config { + cfg.Endpoint = fmt.Sprintf("http://%s", ods.GetEndpoint()) + // Disable retries, we should push data and if error just log it. + cfg.RetrySettings.Enabled = false + // Disable sending queue, we should push data from the caller goroutine. + cfg.QueueSettings.Enabled = false + cfg.TLSSetting = configtls.TLSClientSetting{ + Insecure: true, + } + return cfg +} + +func (ods *otlpHTTPDataSender) GenConfigYAMLStr() string { + // Note that this generates a receiver config for agent. + return fmt.Sprintf(` + otlp: + protocols: + http: + endpoint: "%s"`, ods.GetEndpoint()) +} + +func (ods *otlpHTTPDataSender) ProtocolName() string { + return "otlp" +} + +// OTLPHTTPTraceDataSender implements TraceDataSender for OTLP/HTTP trace exporterType. +type OTLPHTTPTraceDataSender struct { + otlpHTTPDataSender + consumer.TracesConsumer +} + +// Ensure OTLPHTTPTraceDataSender implements TraceDataSender. +var _ TraceDataSender = (*OTLPHTTPTraceDataSender)(nil) + +// NewOTLPHTTPTraceDataSender creates a new TraceDataSender for OTLP/HTTP traces exporterType. +func NewOTLPHTTPTraceDataSender(host string, port int) *OTLPHTTPTraceDataSender { + return &OTLPHTTPTraceDataSender{ + otlpHTTPDataSender: otlpHTTPDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (ote *OTLPHTTPTraceDataSender) Start() error { + factory := otlphttpexporter.NewFactory() + cfg := ote.fillConfig(factory.CreateDefaultConfig().(*otlphttpexporter.Config)) + exp, err := factory.CreateTracesExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + ote.TracesConsumer = exp + return exp.Start(context.Background(), ote) +} + +// OTLPHTTPMetricsDataSender implements MetricDataSender for OTLP/HTTP metrics exporterType. +type OTLPHTTPMetricsDataSender struct { + otlpHTTPDataSender + consumer.MetricsConsumer +} + +// Ensure OTLPHTTPMetricsDataSender implements MetricDataSender. +var _ MetricDataSender = (*OTLPHTTPMetricsDataSender)(nil) + +// NewOTLPHTTPMetricDataSender creates a new OTLP/HTTP metric exporterType sender that will send +// to the specified port after Start is called. +func NewOTLPHTTPMetricDataSender(host string, port int) *OTLPHTTPMetricsDataSender { + return &OTLPHTTPMetricsDataSender{ + otlpHTTPDataSender: otlpHTTPDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (ome *OTLPHTTPMetricsDataSender) Start() error { + factory := otlphttpexporter.NewFactory() + cfg := ome.fillConfig(factory.CreateDefaultConfig().(*otlphttpexporter.Config)) + exp, err := factory.CreateMetricsExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + ome.MetricsConsumer = exp + return exp.Start(context.Background(), ome) +} + +// OTLPHTTPLogsDataSender implements LogsDataSender for OTLP/HTTP logs exporterType. +type OTLPHTTPLogsDataSender struct { + otlpHTTPDataSender + consumer.LogsConsumer +} + +// Ensure OTLPHTTPLogsDataSender implements MetricDataSender. +var _ LogDataSender = (*OTLPHTTPLogsDataSender)(nil) + +// NewOTLPMetricDataSender creates a new OTLP/HTTP metric exporterType sender that will send +// to the specified port after Start is called. +func NewOTLPHTTPLogsDataSender(host string, port int) *OTLPHTTPLogsDataSender { + return &OTLPHTTPLogsDataSender{ + otlpHTTPDataSender: otlpHTTPDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (olds *OTLPHTTPLogsDataSender) Start() error { + factory := otlphttpexporter.NewFactory() + cfg := olds.fillConfig(factory.CreateDefaultConfig().(*otlphttpexporter.Config)) + exp, err := factory.CreateLogsExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + olds.LogsConsumer = exp + return exp.Start(context.Background(), olds) +} + +type otlpDataSender struct { + DataSenderBase +} + +func (ods *otlpDataSender) fillConfig(cfg *otlpexporter.Config) *otlpexporter.Config { + cfg.Endpoint = ods.GetEndpoint() + // Disable retries, we should push data and if error just log it. + cfg.RetrySettings.Enabled = false + // Disable sending queue, we should push data from the caller goroutine. + cfg.QueueSettings.Enabled = false + cfg.TLSSetting = configtls.TLSClientSetting{ + Insecure: true, + } + return cfg +} + +func (ods *otlpDataSender) GenConfigYAMLStr() string { + // Note that this generates a receiver config for agent. + return fmt.Sprintf(` + otlp: + protocols: + grpc: + endpoint: "%s"`, ods.GetEndpoint()) +} + +func (ods *otlpDataSender) ProtocolName() string { + return "otlp" +} + +// OTLPTraceDataSender implements TraceDataSender for OTLP trace exporterType. +type OTLPTraceDataSender struct { + otlpDataSender + consumer.TracesConsumer +} + +// Ensure OTLPTraceDataSender implements TraceDataSender. +var _ TraceDataSender = (*OTLPTraceDataSender)(nil) + +// NewOTLPTraceDataSender creates a new TraceDataSender for OTLP traces exporterType. +func NewOTLPTraceDataSender(host string, port int) *OTLPTraceDataSender { + return &OTLPTraceDataSender{ + otlpDataSender: otlpDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (ote *OTLPTraceDataSender) Start() error { + factory := otlpexporter.NewFactory() + cfg := ote.fillConfig(factory.CreateDefaultConfig().(*otlpexporter.Config)) + exp, err := factory.CreateTracesExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + ote.TracesConsumer = exp + return exp.Start(context.Background(), ote) +} + +// OTLPMetricsDataSender implements MetricDataSender for OTLP metrics exporterType. +type OTLPMetricsDataSender struct { + otlpDataSender + consumer.MetricsConsumer +} + +// Ensure OTLPMetricsDataSender implements MetricDataSender. +var _ MetricDataSender = (*OTLPMetricsDataSender)(nil) + +// NewOTLPMetricDataSender creates a new OTLP metric exporterType sender that will send +// to the specified port after Start is called. +func NewOTLPMetricDataSender(host string, port int) *OTLPMetricsDataSender { + return &OTLPMetricsDataSender{ + otlpDataSender: otlpDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (ome *OTLPMetricsDataSender) Start() error { + factory := otlpexporter.NewFactory() + cfg := ome.fillConfig(factory.CreateDefaultConfig().(*otlpexporter.Config)) + exp, err := factory.CreateMetricsExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + ome.MetricsConsumer = exp + return exp.Start(context.Background(), ome) +} + +// OTLPLogsDataSender implements LogsDataSender for OTLP logs exporterType. +type OTLPLogsDataSender struct { + otlpDataSender + consumer.LogsConsumer +} + +// Ensure OTLPLogsDataSender implements LogDataSender. +var _ LogDataSender = (*OTLPLogsDataSender)(nil) + +// NewOTLPMetricDataSender creates a new OTLP metric exporterType sender that will send +// to the specified port after Start is called. +func NewOTLPLogsDataSender(host string, port int) *OTLPLogsDataSender { + return &OTLPLogsDataSender{ + otlpDataSender: otlpDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + }, + } +} + +func (olds *OTLPLogsDataSender) Start() error { + factory := otlpexporter.NewFactory() + cfg := olds.fillConfig(factory.CreateDefaultConfig().(*otlpexporter.Config)) + exp, err := factory.CreateLogsExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + olds.LogsConsumer = exp + return exp.Start(context.Background(), olds) +} + +// ZipkinDataSender implements TraceDataSender for Zipkin http exporterType. +type ZipkinDataSender struct { + DataSenderBase + consumer.TracesConsumer +} + +// Ensure ZipkinDataSender implements TraceDataSender. +var _ TraceDataSender = (*ZipkinDataSender)(nil) + +// NewZipkinDataSender creates a new Zipkin exporterType sender that will send +// to the specified port after Start is called. +func NewZipkinDataSender(host string, port int) *ZipkinDataSender { + return &ZipkinDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + } +} + +func (zs *ZipkinDataSender) Start() error { + factory := zipkinexporter.NewFactory() + cfg := factory.CreateDefaultConfig().(*zipkinexporter.Config) + cfg.Endpoint = fmt.Sprintf("http://%s/api/v2/spans", zs.GetEndpoint()) + // Disable retries, we should push data and if error just log it. + cfg.RetrySettings.Enabled = false + // Disable sending queue, we should push data from the caller goroutine. + cfg.QueueSettings.Enabled = false + + exp, err := factory.CreateTracesExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + zs.TracesConsumer = exp + return exp.Start(context.Background(), zs) +} + +func (zs *ZipkinDataSender) GenConfigYAMLStr() string { + return fmt.Sprintf(` + zipkin: + endpoint: %s`, zs.GetEndpoint()) +} + +func (zs *ZipkinDataSender) ProtocolName() string { + return "zipkin" +} + +// prometheus + +type PrometheusDataSender struct { + DataSenderBase + consumer.MetricsConsumer + namespace string +} + +var _ MetricDataSender = (*PrometheusDataSender)(nil) + +func NewPrometheusDataSender(host string, port int) *PrometheusDataSender { + return &PrometheusDataSender{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + } +} + +func (pds *PrometheusDataSender) Start() error { + factory := prometheusexporter.NewFactory() + cfg := factory.CreateDefaultConfig().(*prometheusexporter.Config) + cfg.Endpoint = pds.GetEndpoint() + cfg.Namespace = pds.namespace + + exp, err := factory.CreateMetricsExporter(context.Background(), defaultExporterParams(), cfg) + if err != nil { + return err + } + + pds.MetricsConsumer = exp + return exp.Start(context.Background(), pds) +} + +func (pds *PrometheusDataSender) GenConfigYAMLStr() string { + format := ` + prometheus: + config: + scrape_configs: + - job_name: 'testbed' + scrape_interval: 100ms + static_configs: + - targets: ['%s'] +` + return fmt.Sprintf(format, pds.GetEndpoint()) +} + +func (pds *PrometheusDataSender) ProtocolName() string { + return "prometheus" +} + +type FluentBitFileLogWriter struct { + DataSenderBase + file *os.File + parsersFile *os.File +} + +// Ensure FluentBitFileLogWriter implements LogDataSender. +var _ LogDataSender = (*FluentBitFileLogWriter)(nil) + +// NewFluentBitFileLogWriter creates a new data sender that will write log entries to a +// file, to be tailed by FluentBit and sent to the collector. +func NewFluentBitFileLogWriter(host string, port int) *FluentBitFileLogWriter { + file, err := ioutil.TempFile("", "perf-logs.json") + if err != nil { + panic("failed to create temp file") + } + + parsersFile, err := ioutil.TempFile("", "parsers.json") + if err != nil { + panic("failed to create temp file") + } + + f := &FluentBitFileLogWriter{ + DataSenderBase: DataSenderBase{ + Port: port, + Host: host, + }, + file: file, + parsersFile: parsersFile, + } + f.setupParsers() + return f +} + +func (f *FluentBitFileLogWriter) Start() error { + return nil +} + +func (f *FluentBitFileLogWriter) setupParsers() { + _, err := f.parsersFile.Write([]byte(` +[PARSER] + Name json + Format json + Time_Key time + Time_Format %d/%m/%Y:%H:%M:%S %z +`)) + if err != nil { + panic("failed to write parsers") + } + + f.parsersFile.Close() +} + +func (f *FluentBitFileLogWriter) ConsumeLogs(_ context.Context, logs pdata.Logs) error { + for i := 0; i < logs.ResourceLogs().Len(); i++ { + for j := 0; j < logs.ResourceLogs().At(i).InstrumentationLibraryLogs().Len(); j++ { + ills := logs.ResourceLogs().At(i).InstrumentationLibraryLogs().At(j) + for k := 0; k < ills.Logs().Len(); k++ { + _, err := f.file.Write(append(f.convertLogToJSON(ills.Logs().At(k)), '\n')) + if err != nil { + return err + } + } + } + } + return nil +} + +func (f *FluentBitFileLogWriter) convertLogToJSON(lr pdata.LogRecord) []byte { + rec := map[string]string{ + "time": time.Unix(0, int64(lr.Timestamp())).Format("02/01/2006:15:04:05Z"), + } + rec["log"] = lr.Body().StringVal() + + lr.Attributes().ForEach(func(k string, v pdata.AttributeValue) { + switch v.Type() { + case pdata.AttributeValueSTRING: + rec[k] = v.StringVal() + case pdata.AttributeValueINT: + rec[k] = strconv.FormatInt(v.IntVal(), 10) + case pdata.AttributeValueDOUBLE: + rec[k] = strconv.FormatFloat(v.DoubleVal(), 'f', -1, 64) + case pdata.AttributeValueBOOL: + rec[k] = strconv.FormatBool(v.BoolVal()) + default: + panic("missing case") + } + }) + b, err := json.Marshal(rec) + if err != nil { + panic("failed to write log: " + err.Error()) + } + return b +} + +func (f *FluentBitFileLogWriter) Flush() { + _ = f.file.Sync() +} + +func (f *FluentBitFileLogWriter) GenConfigYAMLStr() string { + // Note that this generates a receiver config for agent. + return fmt.Sprintf(` + fluentforward: + endpoint: "%s"`, f.GetEndpoint()) +} + +func (f *FluentBitFileLogWriter) Extensions() map[string]string { + return map[string]string{ + "fluentbit": fmt.Sprintf(` + fluentbit: + executable_path: fluent-bit + tcp_endpoint: "%s" + config: | + [SERVICE] + parsers_file %s + [INPUT] + Name tail + parser json + path %s +`, f.GetEndpoint(), f.parsersFile.Name(), f.file.Name()), + } +} + +func (f *FluentBitFileLogWriter) ProtocolName() string { + return "fluentforward" +} + +func defaultExporterParams() component.ExporterCreateParams { + return component.ExporterCreateParams{Logger: zap.L()} +} diff --git a/internal/otel_collector/testbed/testbed/test_bed.go b/internal/otel_collector/testbed/testbed/test_bed.go new file mode 100644 index 00000000000..e10f5710b6f --- /dev/null +++ b/internal/otel_collector/testbed/testbed/test_bed.go @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package testbed allows to easily set up a test that requires running the agent +// and a load generator, measure and define resource consumption expectations +// for the agent, fail tests automatically when expectations are exceeded. +// +// Each test case requires a agent configuration file and (optionally) load +// generator spec file. Test cases are defined as regular Go tests. +// +// Agent and load generator must be pre-built and their paths must be specified in +// test bed config file. RUN_TESTBED env variable must be defined for tests to run. +package testbed + +import ( + "log" + "os" + "path/filepath" + "testing" +) + +func Start(resultsSummary TestResultsSummary) error { + dir, err := filepath.Abs("results") + if err != nil { + log.Fatalf(err.Error()) + } + resultsSummary.Init(dir) + + return err +} + +func SaveResults(resultsSummary TestResultsSummary) { + resultsSummary.Save() +} + +const testBedEnableEnvVarName = "RUN_TESTBED" + +var GlobalConfig = struct { + // Relative path to default agent executable to test. + // Can be set in the contrib repo to use a different executable name. + // Set this before calling DoTestMain(). + // + // If used in the path, {{.GOOS}} and {{.GOARCH}} will be expanded to the current + // OS and ARCH correspondingly. + // + // Individual tests can override this by setting the AgentExePath of ChildProcess + // that is passed to the TestCase. + DefaultAgentExeRelativeFile string +}{ + // The default exe that is produced by Makefile "otelcol" target relative + // to testbed/tests directory. + DefaultAgentExeRelativeFile: "../../bin/otelcol_{{.GOOS}}_{{.GOARCH}}", +} + +// DoTestMain is intended to be run from TestMain somewhere in the test suit. +// This enables the testbed. +func DoTestMain(m *testing.M, resultsSummary TestResultsSummary) { + testBedConfigFile := os.Getenv(testBedEnableEnvVarName) + if testBedConfigFile == "" { + log.Printf(testBedEnableEnvVarName + " is not defined, skipping E2E tests.") + os.Exit(0) + } + + // Load the test bed config first. + err := Start(resultsSummary) + + if err != nil { + log.Fatalf(err.Error()) + os.Exit(0) + } + + res := m.Run() + + SaveResults(resultsSummary) + + // Now run all tests. + os.Exit(res) +} diff --git a/internal/otel_collector/testbed/testbed/test_case.go b/internal/otel_collector/testbed/testbed/test_case.go new file mode 100644 index 00000000000..8ce5577f8ab --- /dev/null +++ b/internal/otel_collector/testbed/testbed/test_case.go @@ -0,0 +1,355 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "log" + "net" + "os" + "path" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestCase defines a running test case. +type TestCase struct { + t *testing.T + + // Directory where test case results and logs will be written. + resultDir string + + // does not write out results when set to true + skipResults bool + + // Agent config file path. + agentConfigFile string + + // Load generator spec file path. + // loadSpecFile string + + // Resource spec for agent. + resourceSpec ResourceSpec + + // Agent process. + agentProc OtelcolRunner + + Sender DataSender + Receiver DataReceiver + + LoadGenerator *LoadGenerator + MockBackend *MockBackend + validator TestCaseValidator + + startTime time.Time + + // ErrorSignal indicates an error in the test case execution, e.g. process execution + // failure or exceeding resource consumption, etc. The actual error message is already + // logged, this is only an indicator on which you can wait to be informed. + ErrorSignal chan struct{} + + // Duration is the requested duration of the tests. Configured via TESTBED_DURATION + // env variable and defaults to 15 seconds if env variable is unspecified. + Duration time.Duration + + doneSignal chan struct{} + + errorCause string + + resultsSummary TestResultsSummary +} + +const mibibyte = 1024 * 1024 +const testcaseDurationVar = "TESTCASE_DURATION" + +// NewTestCase creates a new TestCase. It expects agent-config.yaml in the specified directory. +func NewTestCase( + t *testing.T, + dataProvider DataProvider, + sender DataSender, + receiver DataReceiver, + agentProc OtelcolRunner, + validator TestCaseValidator, + resultsSummary TestResultsSummary, + opts ...TestCaseOption, +) *TestCase { + tc := TestCase{} + + tc.t = t + tc.ErrorSignal = make(chan struct{}) + tc.doneSignal = make(chan struct{}) + tc.startTime = time.Now() + tc.Sender = sender + tc.Receiver = receiver + tc.agentProc = agentProc + tc.validator = validator + tc.resultsSummary = resultsSummary + + // Get requested test case duration from env variable. + duration := os.Getenv(testcaseDurationVar) + if duration == "" { + duration = "15s" + } + var err error + tc.Duration, err = time.ParseDuration(duration) + if err != nil { + log.Fatalf("Invalid "+testcaseDurationVar+": %v. Expecting a valid duration string.", duration) + } + + // Apply all provided options. + for _, opt := range opts { + opt.Apply(&tc) + } + + // Prepare directory for results. + tc.resultDir, err = filepath.Abs(path.Join("results", t.Name())) + require.NoErrorf(t, err, "Cannot resolve %s", t.Name()) + require.NoErrorf(t, os.MkdirAll(tc.resultDir, os.ModePerm), "Cannot create directory %s", tc.resultDir) + + // Set default resource check period. + tc.resourceSpec.ResourceCheckPeriod = 3 * time.Second + if tc.Duration < tc.resourceSpec.ResourceCheckPeriod { + // Resource check period should not be longer than entire test duration. + tc.resourceSpec.ResourceCheckPeriod = tc.Duration + } + + tc.LoadGenerator, err = NewLoadGenerator(dataProvider, sender) + require.NoError(t, err, "Cannot create generator") + + tc.MockBackend = NewMockBackend(tc.composeTestResultFileName("backend.log"), receiver) + + go tc.logStats() + + return &tc +} + +func (tc *TestCase) composeTestResultFileName(fileName string) string { + fileName, err := filepath.Abs(path.Join(tc.resultDir, fileName)) + require.NoError(tc.t, err, "Cannot resolve %s", fileName) + return fileName +} + +// SetResourceLimits sets expected limits for resource consmption. +// Error is signaled if consumption during ResourceCheckPeriod exceeds the limits. +// Limits are modified only for non-zero fields of resourceSpec, all zero-value fields +// fo resourceSpec are ignored and their previous values remain in effect. +func (tc *TestCase) SetResourceLimits(resourceSpec ResourceSpec) { + if resourceSpec.ExpectedMaxCPU > 0 { + tc.resourceSpec.ExpectedMaxCPU = resourceSpec.ExpectedMaxCPU + } + if resourceSpec.ExpectedMaxRAM > 0 { + tc.resourceSpec.ExpectedMaxRAM = resourceSpec.ExpectedMaxRAM + } + if resourceSpec.ResourceCheckPeriod > 0 { + tc.resourceSpec.ResourceCheckPeriod = resourceSpec.ResourceCheckPeriod + } +} + +// StartAgent starts the agent and redirects its standard output and standard error +// to "agent.log" file located in the test directory. +func (tc *TestCase) StartAgent(args ...string) { + if tc.agentConfigFile != "" { + args = append(args, "--config") + args = append(args, tc.agentConfigFile) + } + logFileName := tc.composeTestResultFileName("agent.log") + + err := tc.agentProc.Start(StartParams{ + Name: "Agent", + LogFilePath: logFileName, + CmdArgs: args, + resourceSpec: &tc.resourceSpec, + }) + + if err != nil { + tc.indicateError(err) + return + } + + // Start watching resource consumption. + go func() { + err := tc.agentProc.WatchResourceConsumption() + if err != nil { + tc.indicateError(err) + } + }() + + endpoint := tc.LoadGenerator.sender.GetEndpoint() + if endpoint != "" { + // Wait for agent to start. We consider the agent started when we can + // connect to the port to which we intend to send load. We only do this + // if the endpoint is not-empty, i.e. the sender does use network (some senders + // like text log writers don't). + tc.WaitFor(func() bool { + _, err := net.Dial("tcp", tc.LoadGenerator.sender.GetEndpoint()) + return err == nil + }) + } +} + +// StopAgent stops agent process. +func (tc *TestCase) StopAgent() { + tc.agentProc.Stop() +} + +// StartLoad starts the load generator and redirects its standard output and standard error +// to "load-generator.log" file located in the test directory. +func (tc *TestCase) StartLoad(options LoadOptions) { + tc.LoadGenerator.Start(options) +} + +// StopLoad stops load generator. +func (tc *TestCase) StopLoad() { + tc.LoadGenerator.Stop() +} + +// StartBackend starts the specified backend type. +func (tc *TestCase) StartBackend() { + require.NoError(tc.t, tc.MockBackend.Start(), "Cannot start backend") +} + +// StopBackend stops the backend. +func (tc *TestCase) StopBackend() { + tc.MockBackend.Stop() +} + +// EnableRecording enables recording of all data received by MockBackend. +func (tc *TestCase) EnableRecording() { + tc.MockBackend.EnableRecording() +} + +// AgentMemoryInfo returns raw memory info struct about the agent +// as returned by github.com/shirou/gopsutil/process +func (tc *TestCase) AgentMemoryInfo() (uint32, uint32, error) { + stat, err := tc.agentProc.GetProcessMon().MemoryInfo() + if err != nil { + return 0, 0, err + } + return uint32(stat.RSS / mibibyte), uint32(stat.VMS / mibibyte), nil +} + +// Stop stops the load generator, the agent and the backend. +func (tc *TestCase) Stop() { + // Stop all components + tc.StopLoad() + tc.StopAgent() + tc.StopBackend() + + // Stop logging + close(tc.doneSignal) + + if tc.skipResults { + return + } + + // Report test results + tc.validator.RecordResults(tc) +} + +// ValidateData validates data received by mock backend against what was generated and sent to the collector +// instance(s) under test by the LoadGenerator. +func (tc *TestCase) ValidateData() { + select { + case <-tc.ErrorSignal: + // Error is already signaled and recorded. Validating data is pointless. + return + default: + } + + tc.validator.Validate(tc) +} + +// Sleep for specified duration or until error is signaled. +func (tc *TestCase) Sleep(d time.Duration) { + select { + case <-time.After(d): + case <-tc.ErrorSignal: + } +} + +// WaitForN the specific condition for up to a specified duration. Records a test error +// if time is out and condition does not become true. If error is signaled +// while waiting the function will return false, but will not record additional +// test error (we assume that signaled error is already recorded in indicateError()). +func (tc *TestCase) WaitForN(cond func() bool, duration time.Duration, errMsg ...interface{}) bool { + startTime := time.Now() + + // Start with 5 ms waiting interval between condition re-evaluation. + waitInterval := time.Millisecond * 5 + + for { + if cond() { + return true + } + + select { + case <-time.After(waitInterval): + case <-tc.ErrorSignal: + return false + } + + // Increase waiting interval exponentially up to 500 ms. + if waitInterval < time.Millisecond*500 { + waitInterval *= 2 + } + + if time.Since(startTime) > duration { + // Waited too long + tc.t.Error("Time out waiting for", errMsg) + return false + } + } +} + +// WaitFor is like WaitForN but with a fixed duration of 10 seconds +func (tc *TestCase) WaitFor(cond func() bool, errMsg ...interface{}) bool { + return tc.WaitForN(cond, time.Second*10, errMsg...) +} + +func (tc *TestCase) indicateError(err error) { + // Print to log for visibility + log.Print(err.Error()) + + // Indicate error for the test + tc.t.Error(err.Error()) + + tc.errorCause = err.Error() + + // Signal the error via channel + close(tc.ErrorSignal) +} + +func (tc *TestCase) logStats() { + t := time.NewTicker(tc.resourceSpec.ResourceCheckPeriod) + defer t.Stop() + + for { + select { + case <-t.C: + tc.logStatsOnce() + case <-tc.doneSignal: + return + } + } +} + +func (tc *TestCase) logStatsOnce() { + log.Printf("%s | %s | %s", + tc.agentProc.GetResourceConsumption(), + tc.LoadGenerator.GetStats(), + tc.MockBackend.GetStats()) +} diff --git a/internal/otel_collector/testbed/testbed/utils.go b/internal/otel_collector/testbed/testbed/utils.go new file mode 100644 index 00000000000..160a2559b9d --- /dev/null +++ b/internal/otel_collector/testbed/testbed/utils.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "testing" + + "go.opentelemetry.io/collector/testutil" +) + +func GetAvailablePort(t *testing.T) int { + return int(testutil.GetAvailablePort(t)) +} diff --git a/internal/otel_collector/testbed/testbed/validator.go b/internal/otel_collector/testbed/testbed/validator.go new file mode 100644 index 00000000000..b18b14e208e --- /dev/null +++ b/internal/otel_collector/testbed/testbed/validator.go @@ -0,0 +1,618 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testbed + +import ( + "encoding/json" + "fmt" + "log" + "reflect" + "sort" + "strings" + "time" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" +) + +// TestCaseValidator defines the interface for validating and reporting test results. +type TestCaseValidator interface { + // Validate executes validation routines and test assertions. + Validate(tc *TestCase) + // RecordResults updates the TestResultsSummary for the test suite with results of a single test. + RecordResults(tc *TestCase) +} + +// PerfTestValidator implements TestCaseValidator for test suites using PerformanceResults for summarizing results. +type PerfTestValidator struct { +} + +func (v *PerfTestValidator) Validate(tc *TestCase) { + if assert.EqualValues(tc.t, tc.LoadGenerator.DataItemsSent(), tc.MockBackend.DataItemsReceived(), + "Received and sent counters do not match.") { + log.Printf("Sent and received data matches.") + } +} + +func (v *PerfTestValidator) RecordResults(tc *TestCase) { + rc := tc.agentProc.GetTotalConsumption() + + var result string + if tc.t.Failed() { + result = "FAIL" + } else { + result = "PASS" + } + + // Remove "Test" prefix from test name. + testName := tc.t.Name()[4:] + + tc.resultsSummary.Add(tc.t.Name(), &PerformanceTestResult{ + testName: testName, + result: result, + receivedSpanCount: tc.MockBackend.DataItemsReceived(), + sentSpanCount: tc.LoadGenerator.DataItemsSent(), + duration: time.Since(tc.startTime), + cpuPercentageAvg: rc.CPUPercentAvg, + cpuPercentageMax: rc.CPUPercentMax, + ramMibAvg: rc.RAMMiBAvg, + ramMibMax: rc.RAMMiBMax, + errorCause: tc.errorCause, + }) +} + +// CorrectnessTestValidator implements TestCaseValidator for test suites using CorrectnessResults for summarizing results. +type CorrectnessTestValidator struct { + dataProvider DataProvider + assertionFailures []*TraceAssertionFailure +} + +func NewCorrectTestValidator(provider DataProvider) *CorrectnessTestValidator { + return &CorrectnessTestValidator{ + dataProvider: provider, + assertionFailures: make([]*TraceAssertionFailure, 0), + } +} + +func (v *CorrectnessTestValidator) Validate(tc *TestCase) { + if assert.EqualValues(tc.t, tc.LoadGenerator.DataItemsSent(), tc.MockBackend.DataItemsReceived(), + "Received and sent counters do not match.") { + log.Printf("Sent and received data counters match.") + } + if len(tc.MockBackend.ReceivedTraces) > 0 { + v.assertSentRecdTracingDataEqual(tc.MockBackend.ReceivedTraces) + } + assert.EqualValues(tc.t, 0, len(v.assertionFailures), "There are span data mismatches.") +} + +func (v *CorrectnessTestValidator) RecordResults(tc *TestCase) { + var result string + if tc.t.Failed() { + result = "FAIL" + } else { + result = "PASS" + } + + // Remove "Test" prefix from test name. + testName := tc.t.Name()[4:] + tc.resultsSummary.Add(tc.t.Name(), &CorrectnessTestResult{ + testName: testName, + result: result, + duration: time.Since(tc.startTime), + receivedSpanCount: tc.MockBackend.DataItemsReceived(), + sentSpanCount: tc.LoadGenerator.DataItemsSent(), + traceAssertionFailureCount: uint64(len(v.assertionFailures)), + traceAssertionFailures: v.assertionFailures, + }) +} + +func (v *CorrectnessTestValidator) assertSentRecdTracingDataEqual(tracesList []pdata.Traces) { + for _, td := range tracesList { + resourceSpansList := pdata.TracesToOtlp(td) + for _, rs := range resourceSpansList { + for _, ils := range rs.InstrumentationLibrarySpans { + for _, recdSpan := range ils.Spans { + sentSpan := v.dataProvider.GetGeneratedSpan(pdata.TraceID(recdSpan.TraceId), pdata.SpanID(recdSpan.SpanId)) + v.diffSpan(sentSpan, recdSpan) + } + } + } + + } +} + +func (v *CorrectnessTestValidator) diffSpan(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan == nil { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: recdSpan.Name, + } + v.assertionFailures = append(v.assertionFailures, af) + return + } + v.diffSpanTraceID(sentSpan, recdSpan) + v.diffSpanSpanID(sentSpan, recdSpan) + v.diffSpanTraceState(sentSpan, recdSpan) + v.diffSpanParentSpanID(sentSpan, recdSpan) + v.diffSpanName(sentSpan, recdSpan) + v.diffSpanKind(sentSpan, recdSpan) + v.diffSpanTimestamps(sentSpan, recdSpan) + v.diffSpanAttributes(sentSpan, recdSpan) + v.diffSpanEvents(sentSpan, recdSpan) + v.diffSpanLinks(sentSpan, recdSpan) + v.diffSpanStatus(sentSpan, recdSpan) +} + +func (v *CorrectnessTestValidator) diffSpanTraceID(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan.TraceId.HexString() != recdSpan.TraceId.HexString() { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "TraceId", + expectedValue: sentSpan.TraceId.HexString(), + actualValue: recdSpan.TraceId.HexString(), + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanSpanID(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan.SpanId.HexString() != recdSpan.SpanId.HexString() { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "SpanId", + expectedValue: sentSpan.SpanId.HexString(), + actualValue: recdSpan.SpanId.HexString(), + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanTraceState(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan.TraceState != recdSpan.TraceState { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "TraceState", + expectedValue: sentSpan.TraceState, + actualValue: recdSpan.TraceState, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanParentSpanID(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan.ParentSpanId.HexString() != recdSpan.ParentSpanId.HexString() { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "ParentSpanId", + expectedValue: sentSpan.ParentSpanId.HexString(), + actualValue: recdSpan.ParentSpanId.HexString(), + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanName(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + // Because of https://github.com/openzipkin/zipkin-go/pull/166 compare lower cases. + if !strings.EqualFold(sentSpan.Name, recdSpan.Name) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "Name", + expectedValue: sentSpan.Name, + actualValue: recdSpan.Name, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanKind(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan.Kind != recdSpan.Kind { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "Kind", + expectedValue: sentSpan.Kind, + actualValue: recdSpan.Kind, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanTimestamps(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if notWithinOneMillisecond(sentSpan.StartTimeUnixNano, recdSpan.StartTimeUnixNano) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "StartTimeUnixNano", + expectedValue: sentSpan.StartTimeUnixNano, + actualValue: recdSpan.StartTimeUnixNano, + } + v.assertionFailures = append(v.assertionFailures, af) + } + if notWithinOneMillisecond(sentSpan.EndTimeUnixNano, recdSpan.EndTimeUnixNano) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "EndTimeUnixNano", + expectedValue: sentSpan.EndTimeUnixNano, + actualValue: recdSpan.EndTimeUnixNano, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanAttributes(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if len(sentSpan.Attributes) != len(recdSpan.Attributes) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "Attributes", + expectedValue: len(sentSpan.Attributes), + actualValue: len(recdSpan.Attributes), + } + v.assertionFailures = append(v.assertionFailures, af) + } else { + sentAttrs := sentSpan.Attributes + recdAttrs := recdSpan.Attributes + v.diffAttributesSlice(sentSpan.Name, recdAttrs, sentAttrs, "Attributes[%s]") + } + if sentSpan.DroppedAttributesCount != recdSpan.DroppedAttributesCount { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "DroppedAttributesCount", + expectedValue: sentSpan.DroppedAttributesCount, + actualValue: recdSpan.DroppedAttributesCount, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanEvents(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if len(sentSpan.Events) != len(recdSpan.Events) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "Events", + expectedValue: len(sentSpan.Events), + actualValue: len(recdSpan.Events), + } + v.assertionFailures = append(v.assertionFailures, af) + } else { + sentEventMap := convertEventsSliceToMap(sentSpan.Events) + recdEventMap := convertEventsSliceToMap(recdSpan.Events) + for name, sentEvents := range sentEventMap { + recdEvents, match := recdEventMap[name] + if match { + match = len(sentEvents) == len(recdEvents) + } + if !match { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: fmt.Sprintf("Events[%s]", name), + expectedValue: len(sentEvents), + actualValue: len(recdEvents), + } + v.assertionFailures = append(v.assertionFailures, af) + } else { + for i, sentEvent := range sentEvents { + recdEvent := recdEvents[i] + if notWithinOneMillisecond(sentEvent.TimeUnixNano, recdEvent.TimeUnixNano) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: fmt.Sprintf("Events[%s].TimeUnixNano", name), + expectedValue: sentEvent.TimeUnixNano, + actualValue: recdEvent.TimeUnixNano, + } + v.assertionFailures = append(v.assertionFailures, af) + } + v.diffAttributesSlice(sentSpan.Name, sentEvent.Attributes, recdEvent.Attributes, + "Events["+name+"].Attributes[%s]") + } + } + } + } + if sentSpan.DroppedEventsCount != recdSpan.DroppedEventsCount { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "DroppedEventsCount", + expectedValue: sentSpan.DroppedEventsCount, + actualValue: recdSpan.DroppedEventsCount, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanLinks(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if len(sentSpan.Links) != len(recdSpan.Links) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "Links", + expectedValue: len(sentSpan.Links), + actualValue: len(recdSpan.Links), + } + v.assertionFailures = append(v.assertionFailures, af) + } else { + recdLinksMap := convertLinksSliceToMap(recdSpan.Links) + for i, sentLink := range sentSpan.Links { + spanID := sentLink.SpanId.HexString() + recdLink, ok := recdLinksMap[spanID] + if ok { + v.diffAttributesSlice(sentSpan.Name, sentLink.Attributes, recdLink.Attributes, + "Links["+spanID+"].Attributes[%s]") + } else { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: fmt.Sprintf("Links[%d]", i), + expectedValue: spanID, + actualValue: "", + } + v.assertionFailures = append(v.assertionFailures, af) + } + + } + } + if sentSpan.DroppedLinksCount != recdSpan.DroppedLinksCount { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "DroppedLinksCount", + expectedValue: sentSpan.DroppedLinksCount, + actualValue: recdSpan.DroppedLinksCount, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffSpanStatus(sentSpan *otlptrace.Span, recdSpan *otlptrace.Span) { + if sentSpan.Status.Code != recdSpan.Status.Code { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: sentSpan.Name, + fieldPath: "Status.Code", + expectedValue: sentSpan.Status.Code, + actualValue: recdSpan.Status.Code, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) diffAttributesSlice(spanName string, recdAttrs []otlpcommon.KeyValue, + sentAttrs []otlpcommon.KeyValue, fmtStr string) { + recdAttrsMap := convertAttributesSliceToMap(recdAttrs) + for _, sentAttr := range sentAttrs { + recdAttr, ok := recdAttrsMap[sentAttr.Key] + if ok { + sentVal := retrieveAttributeValue(sentAttr) + recdVal := retrieveAttributeValue(recdAttr) + switch val := sentVal.(type) { + case *otlpcommon.KeyValueList: + v.compareKeyValueList(spanName, val, recdVal, fmtStr, sentAttr.Key) + case *otlpcommon.ArrayValue: + v.compareArrayList(spanName, val, recdVal, fmtStr, sentAttr.Key) + default: + v.compareSimpleValues(spanName, sentVal, recdVal, fmtStr, sentAttr.Key) + } + + } else { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf("Attributes[%s]", sentAttr.Key), + expectedValue: retrieveAttributeValue(sentAttr), + actualValue: nil, + } + v.assertionFailures = append(v.assertionFailures, af) + } + } +} + +func (v *CorrectnessTestValidator) compareSimpleValues(spanName string, sentVal interface{}, recdVal interface{}, + fmtStr string, attrKey string) { + if !reflect.DeepEqual(sentVal, recdVal) { + sentStr := fmt.Sprintf("%v", sentVal) + recdStr := fmt.Sprintf("%v", recdVal) + if !strings.EqualFold(sentStr, recdStr) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf(fmtStr, attrKey), + expectedValue: sentVal, + actualValue: recdVal, + } + v.assertionFailures = append(v.assertionFailures, af) + } + } +} + +func (v *CorrectnessTestValidator) compareKeyValueList(spanName string, sentKVList *otlpcommon.KeyValueList, + recdVal interface{}, fmtStr string, attrKey string) { + switch val := recdVal.(type) { + case *otlpcommon.KeyValueList: + v.diffAttributesSlice(spanName, val.Values, sentKVList.Values, fmtStr) + case string: + jsonStr := convertKVListToJSONString(sentKVList.Values) + v.compareSimpleValues(spanName, jsonStr, val, fmtStr, attrKey) + default: + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf(fmtStr, attrKey), + expectedValue: sentKVList, + actualValue: recdVal, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) compareArrayList(spanName string, sentArray *otlpcommon.ArrayValue, + recdVal interface{}, fmtStr string, attrKey string) { + switch val := recdVal.(type) { + case *otlpcommon.ArrayValue: + v.compareSimpleValues(spanName, sentArray.Values, val.Values, fmtStr, attrKey) + case string: + jsonStr := convertArrayValuesToJSONString(sentArray.Values) + v.compareSimpleValues(spanName, jsonStr, val, fmtStr, attrKey) + default: + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf(fmtStr, attrKey), + expectedValue: sentArray, + actualValue: recdVal, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func convertAttributesSliceToMap(attributes []otlpcommon.KeyValue) map[string]otlpcommon.KeyValue { + attrMap := make(map[string]otlpcommon.KeyValue) + for _, attr := range attributes { + attrMap[attr.Key] = attr + } + return attrMap +} + +func retrieveAttributeValue(attribute otlpcommon.KeyValue) interface{} { + if attribute.Value.Value == nil { + return nil + } + + var attrVal interface{} + switch val := attribute.Value.Value.(type) { + case *otlpcommon.AnyValue_StringValue: + // Because of https://github.com/openzipkin/zipkin-go/pull/166 compare lower cases. + attrVal = strings.ToLower(val.StringValue) + case *otlpcommon.AnyValue_IntValue: + attrVal = val.IntValue + case *otlpcommon.AnyValue_DoubleValue: + attrVal = val.DoubleValue + case *otlpcommon.AnyValue_BoolValue: + attrVal = val.BoolValue + case *otlpcommon.AnyValue_ArrayValue: + attrVal = val.ArrayValue + case *otlpcommon.AnyValue_KvlistValue: + attrVal = val.KvlistValue + default: + attrVal = nil + } + return attrVal +} + +func convertEventsSliceToMap(events []*otlptrace.Span_Event) map[string][]*otlptrace.Span_Event { + eventMap := make(map[string][]*otlptrace.Span_Event) + for _, event := range events { + evtSlice, ok := eventMap[event.Name] + if !ok { + evtSlice = make([]*otlptrace.Span_Event, 0) + } + eventMap[event.Name] = append(evtSlice, event) + } + for _, eventList := range eventMap { + sortEventsByTimestamp(eventList) + } + return eventMap +} + +func sortEventsByTimestamp(eventList []*otlptrace.Span_Event) { + sort.SliceStable(eventList, func(i, j int) bool { return eventList[i].TimeUnixNano < eventList[j].TimeUnixNano }) +} + +func convertLinksSliceToMap(links []*otlptrace.Span_Link) map[string]*otlptrace.Span_Link { + eventMap := make(map[string]*otlptrace.Span_Link) + for _, link := range links { + eventMap[link.SpanId.HexString()] = link + } + return eventMap +} + +func notWithinOneMillisecond(sentNs uint64, recdNs uint64) bool { + var diff uint64 + if sentNs > recdNs { + diff = sentNs - recdNs + } else { + diff = recdNs - sentNs + } + return diff > uint64(1100000) +} + +func convertKVListToJSONString(values []otlpcommon.KeyValue) string { + jsonStr, err := json.Marshal(convertKVListToRawMap(values)) + if err == nil { + return string(jsonStr) + } + return "" +} + +func convertArrayValuesToJSONString(values []otlpcommon.AnyValue) string { + jsonStr, err := json.Marshal(convertArrayValuesToRawSlice(values)) + if err == nil { + return string(jsonStr) + } + return "" +} + +func convertKVListToRawMap(values []otlpcommon.KeyValue) map[string]interface{} { + rawMap := make(map[string]interface{}) + for i := range values { + kv := &values[i] + switch val := kv.Value.GetValue().(type) { + case *otlpcommon.AnyValue_StringValue: + rawMap[kv.Key] = val.StringValue + case *otlpcommon.AnyValue_IntValue: + rawMap[kv.Key] = val.IntValue + case *otlpcommon.AnyValue_DoubleValue: + rawMap[kv.Key] = val.DoubleValue + case *otlpcommon.AnyValue_BoolValue: + rawMap[kv.Key] = val.BoolValue + case *otlpcommon.AnyValue_KvlistValue: + rawMap[kv.Key] = convertKVListToRawMap(val.KvlistValue.Values) + case *otlpcommon.AnyValue_ArrayValue: + rawMap[kv.Key] = convertArrayValuesToRawSlice(val.ArrayValue.Values) + default: + rawMap[kv.Key] = val + } + } + return rawMap +} + +func convertArrayValuesToRawSlice(values []otlpcommon.AnyValue) []interface{} { + rawSlice := make([]interface{}, 0, len(values)) + for _, v := range values { + switch val := v.GetValue().(type) { + case *otlpcommon.AnyValue_StringValue: + rawSlice = append(rawSlice, val.StringValue) + case *otlpcommon.AnyValue_IntValue: + rawSlice = append(rawSlice, val.IntValue) + case *otlpcommon.AnyValue_DoubleValue: + rawSlice = append(rawSlice, val.DoubleValue) + case *otlpcommon.AnyValue_BoolValue: + rawSlice = append(rawSlice, val.BoolValue) + } + } + return rawSlice +} diff --git a/internal/otel_collector/testbed/tests/.gitignore b/internal/otel_collector/testbed/tests/.gitignore new file mode 100644 index 00000000000..a61c5ef81e8 --- /dev/null +++ b/internal/otel_collector/testbed/tests/.gitignore @@ -0,0 +1,2 @@ +results/* +!results/BASELINE.md diff --git a/internal/otel_collector/testbed/tests/e2e_test.go b/internal/otel_collector/testbed/tests/e2e_test.go new file mode 100644 index 00000000000..5de48f08d6b --- /dev/null +++ b/internal/otel_collector/testbed/tests/e2e_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tests contains test cases. To run the tests go to tests directory and run: +// RUN_TESTBED=1 go test -v + +package tests + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/testbed/testbed" +) + +func TestIdleMode(t *testing.T) { + options := testbed.LoadOptions{DataItemsPerSecond: 10_000, ItemsPerBatch: 10} + dataProvider := testbed.NewPerfTestDataProvider(options) + tc := testbed.NewTestCase( + t, + dataProvider, + testbed.NewJaegerGRPCDataSender(testbed.DefaultHost, testbed.DefaultJaegerPort), + testbed.NewOCDataReceiver(testbed.DefaultOCPort), + &testbed.ChildProcess{}, + &testbed.PerfTestValidator{}, + performanceResultsSummary, + ) + defer tc.Stop() + + tc.SetResourceLimits(testbed.ResourceSpec{ExpectedMaxCPU: 4, ExpectedMaxRAM: 50}) + tc.StartAgent() + + tc.Sleep(tc.Duration) +} + +func TestBallastMemory(t *testing.T) { + tests := []struct { + ballastSize uint32 + maxRSS uint32 + }{ + {100, 50}, + {500, 70}, + {1000, 100}, + } + + options := testbed.LoadOptions{DataItemsPerSecond: 10_000, ItemsPerBatch: 10} + dataProvider := testbed.NewPerfTestDataProvider(options) + for _, test := range tests { + tc := testbed.NewTestCase( + t, + dataProvider, + testbed.NewJaegerGRPCDataSender(testbed.DefaultHost, testbed.DefaultJaegerPort), + testbed.NewOCDataReceiver(testbed.DefaultOCPort), + &testbed.ChildProcess{}, + &testbed.PerfTestValidator{}, + performanceResultsSummary, + testbed.WithSkipResults(), + ) + tc.SetResourceLimits(testbed.ResourceSpec{ExpectedMaxRAM: test.maxRSS}) + + tc.StartAgent("--mem-ballast-size-mib", strconv.Itoa(int(test.ballastSize))) + + var rss, vms uint32 + // It is possible that the process is not ready or the ballast code path + // is not hit immediately so we give the process up to a couple of seconds + // to fire up and setup ballast. 2 seconds is a long time for this case but + // it is short enough to not be annoying if the test fails repeatedly + tc.WaitForN(func() bool { + rss, vms, _ = tc.AgentMemoryInfo() + return vms > test.ballastSize + }, time.Second*2, "VMS must be greater than %d", test.ballastSize) + + assert.True(t, rss <= test.maxRSS, fmt.Sprintf("RSS must be less than or equal to %d", test.maxRSS)) + tc.Stop() + } +} diff --git a/internal/otel_collector/testbed/tests/log_test.go b/internal/otel_collector/testbed/tests/log_test.go new file mode 100644 index 00000000000..60b88f913fb --- /dev/null +++ b/internal/otel_collector/testbed/tests/log_test.go @@ -0,0 +1,84 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +// This file contains Test functions which initiate the tests. The tests can be either +// coded in this file or use scenarios from perf_scenarios.go. + +import ( + "testing" + + "go.opentelemetry.io/collector/testbed/testbed" +) + +func TestLog10kDPS(t *testing.T) { + flw := testbed.NewFluentBitFileLogWriter(testbed.DefaultHost, testbed.GetAvailablePort(t)) + tests := []struct { + name string + sender testbed.DataSender + receiver testbed.DataReceiver + resourceSpec testbed.ResourceSpec + extensions map[string]string + }{ + { + name: "OTLP", + sender: testbed.NewOTLPLogsDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + receiver: testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)), + resourceSpec: testbed.ResourceSpec{ + ExpectedMaxCPU: 20, + ExpectedMaxRAM: 70, + }, + }, + { + name: "OTLP-HTTP", + sender: testbed.NewOTLPHTTPLogsDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + receiver: testbed.NewOTLPHTTPDataReceiver(testbed.GetAvailablePort(t)), + resourceSpec: testbed.ResourceSpec{ + ExpectedMaxCPU: 30, + ExpectedMaxRAM: 70, + }, + }, + { + name: "FluentBitToOTLP", + sender: flw, + receiver: testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)), + resourceSpec: testbed.ResourceSpec{ + ExpectedMaxCPU: 50, + ExpectedMaxRAM: 150, + }, + extensions: flw.Extensions(), + }, + } + + processors := map[string]string{ + "batch": ` + batch: +`, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + Scenario10kItemsPerSecond( + t, + test.sender, + test.receiver, + test.resourceSpec, + performanceResultsSummary, + processors, + test.extensions, + ) + }) + } +} diff --git a/internal/otel_collector/testbed/tests/metric_test.go b/internal/otel_collector/testbed/tests/metric_test.go new file mode 100644 index 00000000000..c33ff5ba792 --- /dev/null +++ b/internal/otel_collector/testbed/tests/metric_test.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +// This file contains Test functions which initiate the tests. The tests can be either +// coded in this file or use scenarios from perf_scenarios.go. + +import ( + "testing" + + "go.opentelemetry.io/collector/testbed/testbed" +) + +func TestMetricNoBackend10kDPSOpenCensus(t *testing.T) { + options := testbed.LoadOptions{DataItemsPerSecond: 10_000, ItemsPerBatch: 10} + dataProvider := testbed.NewPerfTestDataProvider(options) + tc := testbed.NewTestCase( + t, + dataProvider, + testbed.NewOCMetricDataSender(testbed.DefaultHost, 55678), + testbed.NewOCDataReceiver(testbed.DefaultOCPort), + &testbed.ChildProcess{}, + &testbed.PerfTestValidator{}, + performanceResultsSummary, + ) + defer tc.Stop() + + tc.SetResourceLimits(testbed.ResourceSpec{ExpectedMaxCPU: 200, ExpectedMaxRAM: 200}) + tc.StartAgent() + + tc.StartLoad(testbed.LoadOptions{DataItemsPerSecond: 10_000}) + + tc.Sleep(tc.Duration) +} + +func TestMetric10kDPS(t *testing.T) { + tests := []struct { + name string + sender testbed.DataSender + receiver testbed.DataReceiver + resourceSpec testbed.ResourceSpec + }{ + { + "OpenCensus", + testbed.NewOCMetricDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOCDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 85, + ExpectedMaxRAM: 75, + }, + }, + { + "OTLP", + testbed.NewOTLPMetricDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 50, + ExpectedMaxRAM: 60, + }, + }, + { + "OTLP-HTTP", + testbed.NewOTLPHTTPMetricDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOTLPHTTPDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 50, + ExpectedMaxRAM: 60, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + Scenario10kItemsPerSecond( + t, + test.sender, + test.receiver, + test.resourceSpec, + performanceResultsSummary, + nil, + nil, + ) + }) + } + +} diff --git a/internal/otel_collector/testbed/tests/resource_processor_test.go b/internal/otel_collector/testbed/tests/resource_processor_test.go new file mode 100644 index 00000000000..c1c6dfc95b5 --- /dev/null +++ b/internal/otel_collector/testbed/tests/resource_processor_test.go @@ -0,0 +1,291 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +import ( + "context" + "path" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" + otlpmetrics "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1" + otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" + "go.opentelemetry.io/collector/testbed/testbed" +) + +var ( + mockedConsumedResourceWithType = &otlpmetrics.ResourceMetrics{ + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "opencensus.resourcetype", + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "host", + }, + }, + }, + { + Key: "label-key", + Value: otlpcommon.AnyValue{ + Value: &otlpcommon.AnyValue_StringValue{ + StringValue: "label-value", + }, + }, + }, + }, + }, + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + { + Name: "metric-name", + Description: "metric-description", + Unit: "metric-unit", + Data: &otlpmetrics.Metric_IntGauge{ + IntGauge: &otlpmetrics.IntGauge{ + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Value: 0, + }, + }, + }, + }, + }, + }, + }, + }, + } + + mockedConsumedResourceNil = &otlpmetrics.ResourceMetrics{ + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + { + Name: "metric-name", + Description: "metric-description", + Unit: "metric-unit", + Data: &otlpmetrics.Metric_IntGauge{ + IntGauge: &otlpmetrics.IntGauge{ + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Value: 0, + }, + }, + }, + }, + }, + }, + }, + }, + } + + mockedConsumedResourceWithoutAttributes = &otlpmetrics.ResourceMetrics{ + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{}, + }, + InstrumentationLibraryMetrics: []*otlpmetrics.InstrumentationLibraryMetrics{ + { + Metrics: []*otlpmetrics.Metric{ + { + Name: "metric-name", + Description: "metric-description", + Unit: "metric-unit", + Data: &otlpmetrics.Metric_IntGauge{ + IntGauge: &otlpmetrics.IntGauge{ + DataPoints: []*otlpmetrics.IntDataPoint{ + { + Value: 0, + }, + }, + }, + }, + }, + }, + }, + }, + } +) + +type resourceProcessorTestCase struct { + name string + resourceProcessorConfig string + mockedConsumedMetricData pdata.Metrics + expectedMetricData pdata.Metrics +} + +func getResourceProcessorTestCases() []resourceProcessorTestCase { + + tests := []resourceProcessorTestCase{ + { + name: "update_and_rename_existing_attributes", + resourceProcessorConfig: ` + resource: + attributes: + - key: label-key + value: new-label-value + action: update + - key: resource-type + from_attribute: opencensus.resourcetype + action: upsert + - key: opencensus.resourcetype + action: delete +`, + mockedConsumedMetricData: getMetricDataFrom(mockedConsumedResourceWithType), + expectedMetricData: getMetricDataFromResourceMetrics(&otlpmetrics.ResourceMetrics{ + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "resource-type", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "host"}}, + }, + { + Key: "label-key", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "new-label-value"}}, + }, + }, + }, + }), + }, + { + name: "set_attribute_on_nil_resource", + resourceProcessorConfig: ` + resource: + attributes: + - key: additional-label-key + value: additional-label-value + action: insert + +`, + mockedConsumedMetricData: getMetricDataFrom(mockedConsumedResourceNil), + expectedMetricData: getMetricDataFromResourceMetrics(&otlpmetrics.ResourceMetrics{ + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "additional-label-key", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "additional-label-value"}}, + }, + }, + }, + }), + }, + { + name: "set_attribute_on_empty_resource", + resourceProcessorConfig: ` + resource: + attributes: + - key: additional-label-key + value: additional-label-value + action: insert +`, + mockedConsumedMetricData: getMetricDataFrom(mockedConsumedResourceWithoutAttributes), + expectedMetricData: getMetricDataFromResourceMetrics(&otlpmetrics.ResourceMetrics{ + Resource: otlpresource.Resource{ + Attributes: []otlpcommon.KeyValue{ + { + Key: "additional-label-key", + Value: otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "additional-label-value"}}, + }, + }, + }, + }), + }, + } + + return tests +} + +func getMetricDataFromResourceMetrics(rm *otlpmetrics.ResourceMetrics) pdata.Metrics { + return pdata.MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{rm}) +} + +func getMetricDataFrom(rm *otlpmetrics.ResourceMetrics) pdata.Metrics { + return pdata.MetricsFromOtlp([]*otlpmetrics.ResourceMetrics{rm}) +} + +func TestMetricResourceProcessor(t *testing.T) { + sender := testbed.NewOTLPMetricDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + receiver := testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)) + + tests := getResourceProcessorTestCases() + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resultDir, err := filepath.Abs(path.Join("results", t.Name())) + require.NoError(t, err) + + agentProc := &testbed.ChildProcess{} + processors := map[string]string{ + "resource": test.resourceProcessorConfig, + } + configStr := createConfigYaml(t, sender, receiver, resultDir, processors, nil) + configCleanup, err := agentProc.PrepareConfig(configStr) + require.NoError(t, err) + defer configCleanup() + + options := testbed.LoadOptions{DataItemsPerSecond: 10000, ItemsPerBatch: 10} + dataProvider := testbed.NewPerfTestDataProvider(options) + tc := testbed.NewTestCase( + t, + dataProvider, + sender, + receiver, + agentProc, + &testbed.PerfTestValidator{}, + performanceResultsSummary, + ) + defer tc.Stop() + + tc.StartBackend() + tc.StartAgent() + defer tc.StopAgent() + + tc.EnableRecording() + + require.NoError(t, sender.Start()) + + // Clear previously received metrics. + tc.MockBackend.ClearReceivedItems() + startCounter := tc.MockBackend.DataItemsReceived() + + sender, ok := tc.Sender.(testbed.MetricDataSender) + require.True(t, ok, "unsupported metric sender") + + require.NoError(t, sender.ConsumeMetrics(context.Background(), test.mockedConsumedMetricData)) + + // We bypass the load generator in this test, but make sure to increment the + // counter since it is used in final reports. + tc.LoadGenerator.IncDataItemsSent() + + tc.WaitFor(func() bool { return tc.MockBackend.DataItemsReceived() == startCounter+1 }, + "datapoints received") + + // Assert Resources + m := tc.MockBackend.ReceivedMetrics[0] + rm := m.ResourceMetrics() + require.Equal(t, 1, rm.Len()) + + expectidMD := test.expectedMetricData + require.Equal(t, + attributesToMap(expectidMD.ResourceMetrics().At(0).Resource().Attributes()), + attributesToMap(rm.At(0).Resource().Attributes()), + ) + }) + } +} diff --git a/internal/otel_collector/testbed/tests/results/BASELINE.md b/internal/otel_collector/testbed/tests/results/BASELINE.md new file mode 100644 index 00000000000..d8e233c018a --- /dev/null +++ b/internal/otel_collector/testbed/tests/results/BASELINE.md @@ -0,0 +1,25 @@ +# Test Results +Started: Fri, 13 Dec 2019 09:20:14 -0500 + +Test |Result|Duration|CPU Avg%|CPU Max%|RAM Avg MiB|RAM Max MiB|Sent Items|Received Items| +----------------------------------------|------|-------:|-------:|-------:|----------:|----------:|---------:|-------------:| +IdleMode |PASS | 15s| 1.3| 4.6| 17| 21| 0| 0| +MetricNoBackend10kDPSOpenCensus |PASS | 15s| 19.9| 22.2| 23| 28| 149940| 0| +Metric10kDPS/OpenCensus |PASS | 18s| 9.6| 11.3| 26| 33| 149900| 149900| +Trace10kSPS/JaegerReceiver |PASS | 16s| 28.9| 31.5| 46| 56| 148830| 148830| +Trace10kSPS/OpenCensusReceiver |PASS | 16s| 27.8| 30.1| 38| 46| 149340| 149340| +TraceNoBackend10kSPSJaeger |PASS | 15s| 25.7| 28.1| 99| 138| 148690| 0| +Trace1kSPSWithAttrs/0*0bytes |PASS | 15s| 16.8| 19.3| 22| 27| 15000| 15000| +Trace1kSPSWithAttrs/100*50bytes |PASS | 15s| 59.9| 65.0| 24| 30| 13920| 13920| +Trace1kSPSWithAttrs/10*1000bytes |PASS | 15s| 49.0| 59.4| 24| 30| 14370| 14370| +Trace1kSPSWithAttrs/20*5000bytes |PASS | 15s| 108.2| 114.1| 38| 53| 14730| 14730| +TraceBallast1kSPSWithAttrs/0*0bytes |PASS | 15s| 16.7| 18.4| 85| 136| 15000| 15000| +TraceBallast1kSPSWithAttrs/100*50bytes |PASS | 15s| 41.0| 47.6| 628| 975| 13900| 13900| +TraceBallast1kSPSWithAttrs/10*1000bytes |PASS | 15s| 36.3| 40.3| 448| 757| 14910| 14910| +TraceBallast1kSPSWithAttrs/20*5000bytes |PASS | 15s| 77.2| 84.5| 879| 1077| 14070| 14070| +TraceBallast1kSPSAddAttrs/0*0bytes |PASS | 15s| 17.1| 18.2| 90| 147| 15000| 15000| +TraceBallast1kSPSAddAttrs/100*50bytes |PASS | 15s| 47.1| 49.3| 676| 979| 14820| 14820| +TraceBallast1kSPSAddAttrs/10*1000bytes |PASS | 15s| 37.6| 40.0| 516| 838| 15000| 15000| +TraceBallast1kSPSAddAttrs/20*5000bytes |PASS | 15s| 53.8| 69.0| 823| 1049| 11740| 11740| + +Total duration: 278s diff --git a/internal/otel_collector/testbed/tests/runtests.sh b/internal/otel_collector/testbed/tests/runtests.sh new file mode 100644 index 00000000000..ecc478acbfd --- /dev/null +++ b/internal/otel_collector/testbed/tests/runtests.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +SED="sed" + +PASS_COLOR=$(printf "\033[32mPASS\033[0m") +FAIL_COLOR=$(printf "\033[31mFAIL\033[0m") +TEST_COLORIZE="${SED} 's/PASS/${PASS_COLOR}/' | ${SED} 's/FAIL/${FAIL_COLOR}/'" +echo ${TEST_ARGS} +RUN_TESTBED=1 go test -v ${TEST_ARGS} 2>&1 | tee results/testoutput.log | bash -c "${TEST_COLORIZE}" + +testStatus=${PIPESTATUS[0]} + +mkdir -p results/junit +go-junit-report < results/testoutput.log > results/junit/results.xml + +bash -c "cat results/TESTRESULTS.md | ${TEST_COLORIZE}" + +exit ${testStatus} diff --git a/internal/otel_collector/testbed/tests/scenarios.go b/internal/otel_collector/testbed/tests/scenarios.go new file mode 100644 index 00000000000..cb8f5e87887 --- /dev/null +++ b/internal/otel_collector/testbed/tests/scenarios.go @@ -0,0 +1,333 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +// This file defines parametrized test scenarios and makes them public so that they can be +// also used by tests in custom builds of Collector (e.g. Collector Contrib). + +import ( + "fmt" + "math/rand" + "path" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/testbed/testbed" +) + +var ( + performanceResultsSummary testbed.TestResultsSummary = &testbed.PerformanceResults{} +) + +// createConfigYaml creates a collector config file that corresponds to the +// sender and receiver used in the test and returns the config file name. +// Map of processor names to their configs. Config is in YAML and must be +// indented by 2 spaces. Processors will be placed between batch and queue for traces +// pipeline. For metrics pipeline these will be sole processors. +func createConfigYaml( + t *testing.T, + sender testbed.DataSender, + receiver testbed.DataReceiver, + resultDir string, + processors map[string]string, + extensions map[string]string, +) string { + + // Create a config. Note that our DataSender is used to generate a config for Collector's + // receiver and our DataReceiver is used to generate a config for Collector's exporter. + // This is because our DataSender sends to Collector's receiver and our DataReceiver + // receives from Collector's exporter. + + // Prepare extra processor config section and comma-separated list of extra processor + // names to use in corresponding "processors" settings. + processorsSections := "" + processorsList := "" + if len(processors) > 0 { + first := true + for name, cfg := range processors { + processorsSections += cfg + "\n" + if !first { + processorsList += "," + } + processorsList += name + first = false + } + } + + // Prepare extra extension config section and comma-separated list of extra extension + // names to use in corresponding "extensions" settings. + extensionsSections := "" + extensionsList := "" + if len(extensions) > 0 { + first := true + for name, cfg := range extensions { + extensionsSections += cfg + "\n" + if !first { + extensionsList += "," + } + extensionsList += name + first = false + } + } + + // Set pipeline based on DataSender type + var pipeline string + switch sender.(type) { + case testbed.TraceDataSender: + pipeline = "traces" + case testbed.MetricDataSender: + pipeline = "metrics" + case testbed.LogDataSender: + pipeline = "logs" + default: + t.Error("Invalid DataSender type") + } + + format := ` +receivers:%v +exporters:%v +processors: + %s + +extensions: + pprof: + save_to_file: %v/cpu.prof + %s + +service: + extensions: [pprof, %s] + pipelines: + %s: + receivers: [%v] + processors: [%s] + exporters: [%v] +` + + // Put corresponding elements into the config template to generate the final config. + return fmt.Sprintf( + format, + sender.GenConfigYAMLStr(), + receiver.GenConfigYAMLStr(), + processorsSections, + resultDir, + extensionsSections, + extensionsList, + pipeline, + sender.ProtocolName(), + processorsList, + receiver.ProtocolName(), + ) +} + +// Run 10k data items/sec test using specified sender and receiver protocols. +func Scenario10kItemsPerSecond( + t *testing.T, + sender testbed.DataSender, + receiver testbed.DataReceiver, + resourceSpec testbed.ResourceSpec, + resultsSummary testbed.TestResultsSummary, + processors map[string]string, + extensions map[string]string, +) { + resultDir, err := filepath.Abs(path.Join("results", t.Name())) + require.NoError(t, err) + + options := testbed.LoadOptions{ + DataItemsPerSecond: 10_000, + ItemsPerBatch: 100, + Parallel: 1, + } + agentProc := &testbed.ChildProcess{} + + configStr := createConfigYaml(t, sender, receiver, resultDir, processors, extensions) + configCleanup, err := agentProc.PrepareConfig(configStr) + require.NoError(t, err) + defer configCleanup() + + dataProvider := testbed.NewPerfTestDataProvider(options) + tc := testbed.NewTestCase( + t, + dataProvider, + sender, + receiver, + agentProc, + &testbed.PerfTestValidator{}, + resultsSummary, + ) + defer tc.Stop() + + tc.SetResourceLimits(resourceSpec) + tc.StartBackend() + tc.StartAgent("--log-level=debug") + + tc.StartLoad(options) + + tc.Sleep(tc.Duration) + + tc.StopLoad() + + tc.WaitFor(func() bool { return tc.LoadGenerator.DataItemsSent() > 0 }, "load generator started") + tc.WaitFor(func() bool { return tc.LoadGenerator.DataItemsSent() == tc.MockBackend.DataItemsReceived() }, + "all data items received") + + tc.StopAgent() + + tc.ValidateData() +} + +// TestCase for Scenario1kSPSWithAttrs func. +type TestCase struct { + attrCount int + attrSizeByte int + expectedMaxCPU uint32 + expectedMaxRAM uint32 + resultsSummary testbed.TestResultsSummary +} + +func genRandByteString(len int) string { + b := make([]byte, len) + for i := range b { + b[i] = byte(rand.Intn(128)) + } + return string(b) +} + +// Scenario1kSPSWithAttrs runs a performance test at 1k sps with specified span attributes +// and test options. +func Scenario1kSPSWithAttrs(t *testing.T, args []string, tests []TestCase, processors map[string]string) { + for i := range tests { + test := tests[i] + + t.Run(fmt.Sprintf("%d*%dbytes", test.attrCount, test.attrSizeByte), func(t *testing.T) { + + options := constructLoadOptions(test) + + agentProc := &testbed.ChildProcess{} + + // Prepare results dir. + resultDir, err := filepath.Abs(path.Join("results", t.Name())) + require.NoError(t, err) + + // Create sender and receiver on available ports. + sender := testbed.NewJaegerGRPCDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)) + receiver := testbed.NewOCDataReceiver(testbed.GetAvailablePort(t)) + + // Prepare config. + configStr := createConfigYaml(t, sender, receiver, resultDir, processors, nil) + configCleanup, err := agentProc.PrepareConfig(configStr) + require.NoError(t, err) + defer configCleanup() + + tc := testbed.NewTestCase( + t, + testbed.NewPerfTestDataProvider(options), + sender, + receiver, + agentProc, + &testbed.PerfTestValidator{}, + test.resultsSummary, + ) + defer tc.Stop() + + tc.SetResourceLimits(testbed.ResourceSpec{ + ExpectedMaxCPU: test.expectedMaxCPU, + ExpectedMaxRAM: test.expectedMaxRAM, + }) + + tc.StartBackend() + tc.StartAgent(args...) + + tc.StartLoad(options) + tc.Sleep(tc.Duration) + tc.StopLoad() + + tc.WaitFor(func() bool { return tc.LoadGenerator.DataItemsSent() > 0 }, "load generator started") + tc.WaitFor(func() bool { return tc.LoadGenerator.DataItemsSent() == tc.MockBackend.DataItemsReceived() }, + "all spans received") + + tc.StopAgent() + + tc.ValidateData() + }) + } +} + +// Structure used for TestTraceNoBackend10kSPS. +// Defines RAM usage range for defined processor type. +type processorConfig struct { + Name string + // map of processor types to their config YAML to use. + Processor map[string]string + ExpectedMaxRAM uint32 + ExpectedMinFinalRAM uint32 +} + +func ScenarioTestTraceNoBackend10kSPS( + t *testing.T, + sender testbed.DataSender, + receiver testbed.DataReceiver, + resourceSpec testbed.ResourceSpec, + resultsSummary testbed.TestResultsSummary, + configuration processorConfig, +) { + + resultDir, err := filepath.Abs(path.Join("results", t.Name())) + require.NoError(t, err) + + options := testbed.LoadOptions{DataItemsPerSecond: 10000, ItemsPerBatch: 10} + agentProc := &testbed.ChildProcess{} + configStr := createConfigYaml(t, sender, receiver, resultDir, configuration.Processor, nil) + configCleanup, err := agentProc.PrepareConfig(configStr) + require.NoError(t, err) + defer configCleanup() + + dataProvider := testbed.NewPerfTestDataProvider(options) + tc := testbed.NewTestCase( + t, + dataProvider, + sender, + receiver, + agentProc, + &testbed.PerfTestValidator{}, + resultsSummary, + ) + + defer tc.Stop() + + tc.SetResourceLimits(resourceSpec) + + tc.StartAgent() + tc.StartLoad(options) + + tc.Sleep(tc.Duration) + + rss, _, _ := tc.AgentMemoryInfo() + assert.Less(t, configuration.ExpectedMinFinalRAM, rss) +} + +func constructLoadOptions(test TestCase) testbed.LoadOptions { + options := testbed.LoadOptions{DataItemsPerSecond: 1000, ItemsPerBatch: 10} + options.Attributes = make(map[string]string) + + // Generate attributes. + for i := 0; i < test.attrCount; i++ { + attrName := genRandByteString(rand.Intn(199) + 1) + options.Attributes[attrName] = genRandByteString(rand.Intn(test.attrSizeByte*2-1) + 1) + } + return options +} diff --git a/internal/otel_collector/testbed/tests/testdata/agent-config.yaml b/internal/otel_collector/testbed/tests/testdata/agent-config.yaml new file mode 100644 index 00000000000..48c9ff2a1b7 --- /dev/null +++ b/internal/otel_collector/testbed/tests/testdata/agent-config.yaml @@ -0,0 +1,27 @@ +receivers: + jaeger: + protocols: + grpc: + endpoint: "localhost:14250" + opencensus: + endpoint: "localhost:55678" + +exporters: + opencensus: + endpoint: "localhost:56565" + insecure: true + logging: + loglevel: info + +processors: + queued_retry: + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [queued_retry] + exporters: [opencensus,logging] + metrics: + receivers: [opencensus] + exporters: [opencensus,logging] diff --git a/internal/otel_collector/testbed/tests/testdata/memory-limiter.yaml b/internal/otel_collector/testbed/tests/testdata/memory-limiter.yaml new file mode 100644 index 00000000000..2449b3548e8 --- /dev/null +++ b/internal/otel_collector/testbed/tests/testdata/memory-limiter.yaml @@ -0,0 +1,30 @@ +receivers: + jaeger: + protocols: + grpc: + endpoint: "localhost:14250" + opencensus: + endpoint: "localhost:55678" + +exporters: + opencensus: + endpoint: "localhost:56565" + insecure: true + logging: + loglevel: info + +processors: + queued_retry: + memory_limiter: + check_interval: 1s + limit_mib: 10 + +service: + pipelines: + traces: + receivers: [jaeger] + processors: [memory_limiter,queued_retry] + exporters: [opencensus,logging] + metrics: + receivers: [opencensus] + exporters: [opencensus,logging] diff --git a/internal/otel_collector/testbed/tests/trace_test.go b/internal/otel_collector/testbed/tests/trace_test.go new file mode 100644 index 00000000000..b65ea31fca5 --- /dev/null +++ b/internal/otel_collector/testbed/tests/trace_test.go @@ -0,0 +1,456 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tests contains test cases. To run the tests go to tests directory and run: +// RUN_TESTBED=1 go test -v + +package tests + +// This file contains Test functions which initiate the tests. The tests can be either +// coded in this file or use scenarios from perf_scenarios.go. + +import ( + "context" + "path" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/testbed/testbed" + "go.opentelemetry.io/collector/translator/conventions" +) + +// TestMain is used to initiate setup, execution and tear down of testbed. +func TestMain(m *testing.M) { + testbed.DoTestMain(m, performanceResultsSummary) +} + +func TestTrace10kSPS(t *testing.T) { + tests := []struct { + name string + sender testbed.DataSender + receiver testbed.DataReceiver + resourceSpec testbed.ResourceSpec + }{ + { + "JaegerGRPC", + testbed.NewJaegerGRPCDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewJaegerDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 40, + ExpectedMaxRAM: 70, + }, + }, + { + "OpenCensus", + testbed.NewOCTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOCDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 39, + ExpectedMaxRAM: 82, + }, + }, + { + "OTLP", + testbed.NewOTLPTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 20, + ExpectedMaxRAM: 70, + }, + }, + { + "OTLP-HTTP", + testbed.NewOTLPHTTPTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOTLPHTTPDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 20, + ExpectedMaxRAM: 100, + }, + }, + { + "Zipkin", + testbed.NewZipkinDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewZipkinDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 80, + ExpectedMaxRAM: 80, + }, + }, + } + + processors := map[string]string{ + "batch": ` + batch: +`, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + Scenario10kItemsPerSecond( + t, + test.sender, + test.receiver, + test.resourceSpec, + performanceResultsSummary, + processors, + nil, + ) + }) + } +} + +func TestTraceNoBackend10kSPS(t *testing.T) { + + limitProcessors := map[string]string{ + "memory_limiter": ` + memory_limiter: + check_interval: 100ms + limit_mib: 20 +`, + } + + noLimitProcessors := map[string]string{} + + var processorsConfig = []processorConfig{ + { + Name: "NoMemoryLimit", + Processor: noLimitProcessors, + ExpectedMaxRAM: 150, + ExpectedMinFinalRAM: 100, + }, + { + Name: "MemoryLimit", + Processor: limitProcessors, + ExpectedMaxRAM: 70, + ExpectedMinFinalRAM: 40, + }, + } + + for _, testConf := range processorsConfig { + t.Run(testConf.Name, func(t *testing.T) { + ScenarioTestTraceNoBackend10kSPS( + t, + testbed.NewOTLPTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)), + testbed.ResourceSpec{ + ExpectedMaxCPU: 50, + ExpectedMaxRAM: testConf.ExpectedMaxRAM, + }, + performanceResultsSummary, + testConf, + ) + }) + } +} + +func TestTrace1kSPSWithAttrs(t *testing.T) { + Scenario1kSPSWithAttrs(t, []string{}, []TestCase{ + // No attributes. + { + attrCount: 0, + attrSizeByte: 0, + expectedMaxCPU: 30, + expectedMaxRAM: 100, + resultsSummary: performanceResultsSummary, + }, + + // We generate 10 attributes each with average key length of 100 bytes and + // average value length of 50 bytes so total size of attributes values is + // 15000 bytes. + { + attrCount: 100, + attrSizeByte: 50, + expectedMaxCPU: 120, + expectedMaxRAM: 100, + resultsSummary: performanceResultsSummary, + }, + + // Approx 10 KiB attributes. + { + attrCount: 10, + attrSizeByte: 1000, + expectedMaxCPU: 100, + expectedMaxRAM: 100, + resultsSummary: performanceResultsSummary, + }, + + // Approx 100 KiB attributes. + { + attrCount: 20, + attrSizeByte: 5000, + expectedMaxCPU: 250, + expectedMaxRAM: 100, + resultsSummary: performanceResultsSummary, + }, + }, nil) +} + +func TestTraceBallast1kSPSWithAttrs(t *testing.T) { + args := []string{"--mem-ballast-size-mib", "1000"} + Scenario1kSPSWithAttrs(t, args, []TestCase{ + // No attributes. + { + attrCount: 0, + attrSizeByte: 0, + expectedMaxCPU: 30, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + { + attrCount: 100, + attrSizeByte: 50, + expectedMaxCPU: 80, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + { + attrCount: 10, + attrSizeByte: 1000, + expectedMaxCPU: 80, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + { + attrCount: 20, + attrSizeByte: 5000, + expectedMaxCPU: 120, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + }, nil) +} + +func TestTraceBallast1kSPSAddAttrs(t *testing.T) { + args := []string{"--mem-ballast-size-mib", "1000"} + + attrProcCfg := ` + attributes: + actions: + - key: attrib.key00 + value: 123 + action: insert + - key: attrib.key01 + value: "a small string for this attribute" + action: insert + - key: attrib.key02 + value: true + action: insert + - key: region + value: test-region + action: insert + - key: data-center + value: test-datacenter + action: insert` + + Scenario1kSPSWithAttrs( + t, + args, + []TestCase{ + { + attrCount: 0, + attrSizeByte: 0, + expectedMaxCPU: 30, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + { + attrCount: 100, + attrSizeByte: 50, + expectedMaxCPU: 80, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + { + attrCount: 10, + attrSizeByte: 1000, + expectedMaxCPU: 80, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + { + attrCount: 20, + attrSizeByte: 5000, + expectedMaxCPU: 120, + expectedMaxRAM: 2000, + resultsSummary: performanceResultsSummary, + }, + }, + map[string]string{"attributes": attrProcCfg}, + ) +} + +// verifySingleSpan sends a single span to Collector, waits until the span is forwarded +// and received by MockBackend and calls user-supplied verification functions on +// received span. +// Temporarily, we need two verification functions in order to verify spans in +// new and old format received by MockBackend. +func verifySingleSpan( + t *testing.T, + tc *testbed.TestCase, + serviceName string, + spanName string, + verifyReceived func(span pdata.Span), +) { + + // Clear previously received traces. + tc.MockBackend.ClearReceivedItems() + startCounter := tc.MockBackend.DataItemsReceived() + + // Send one span. + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + td.ResourceSpans().At(0).Resource().Attributes().InitFromMap(map[string]pdata.AttributeValue{ + conventions.AttributeServiceName: pdata.NewAttributeValueString(serviceName), + }) + td.ResourceSpans().At(0).InstrumentationLibrarySpans().Resize(1) + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + spans.Resize(1) + spans.At(0).SetTraceID(testbed.GenerateSequentialTraceID(1)) + spans.At(0).SetSpanID(testbed.GenerateSequentialSpanID(1)) + spans.At(0).SetName(spanName) + + sender := tc.Sender.(testbed.TraceDataSender) + require.NoError(t, sender.ConsumeTraces(context.Background(), td)) + + // We bypass the load generator in this test, but make sure to increment the + // counter since it is used in final reports. + tc.LoadGenerator.IncDataItemsSent() + + // Wait until span is received. + tc.WaitFor(func() bool { return tc.MockBackend.DataItemsReceived() == startCounter+1 }, + "span received") + + // Verify received span. + count := 0 + for _, td := range tc.MockBackend.ReceivedTraces { + rs := td.ResourceSpans() + for i := 0; i < rs.Len(); i++ { + ils := rs.At(i).InstrumentationLibrarySpans() + for j := 0; j < ils.Len(); j++ { + spans := ils.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + verifyReceived(spans.At(k)) + count++ + } + } + } + } + assert.EqualValues(t, 1, count, "must receive one span") +} + +func TestTraceAttributesProcessor(t *testing.T) { + tests := []struct { + name string + sender testbed.DataSender + receiver testbed.DataReceiver + }{ + { + "JaegerGRPC", + testbed.NewJaegerGRPCDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewJaegerDataReceiver(testbed.GetAvailablePort(t)), + }, + { + "OTLP", + testbed.NewOTLPTraceDataSender(testbed.DefaultHost, testbed.GetAvailablePort(t)), + testbed.NewOTLPDataReceiver(testbed.GetAvailablePort(t)), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resultDir, err := filepath.Abs(path.Join("results", t.Name())) + require.NoError(t, err) + + // Use processor to add attributes to certain spans. + processors := map[string]string{ + "batch": ` + batch: +`, + "attributes": ` + attributes: + include: + match_type: regexp + services: ["service-to-add.*"] + span_names: ["span-to-add-.*"] + actions: + - action: insert + key: "new_attr" + value: "string value" +`, + } + + agentProc := &testbed.ChildProcess{} + configStr := createConfigYaml(t, test.sender, test.receiver, resultDir, processors, nil) + configCleanup, err := agentProc.PrepareConfig(configStr) + require.NoError(t, err) + defer configCleanup() + + options := testbed.LoadOptions{DataItemsPerSecond: 10000, ItemsPerBatch: 10} + dataProvider := testbed.NewPerfTestDataProvider(options) + tc := testbed.NewTestCase( + t, + dataProvider, + test.sender, + test.receiver, + agentProc, + &testbed.PerfTestValidator{}, + performanceResultsSummary, + ) + defer tc.Stop() + + tc.StartBackend() + tc.StartAgent() + defer tc.StopAgent() + + tc.EnableRecording() + + require.NoError(t, test.sender.Start()) + + // Create a span that matches "include" filter. + spanToInclude := "span-to-add-attr" + // Create a service name that matches "include" filter. + nodeToInclude := "service-to-add-attr" + + // verifySpan verifies that attributes was added to the internal data span. + verifySpan := func(span pdata.Span) { + require.NotNil(t, span) + require.Equal(t, span.Attributes().Len(), 1) + attrVal, ok := span.Attributes().Get("new_attr") + assert.True(t, ok) + assert.EqualValues(t, "string value", attrVal.StringVal()) + } + + verifySingleSpan(t, tc, nodeToInclude, spanToInclude, verifySpan) + + // Create a service name that does not match "include" filter. + nodeToExclude := "service-not-to-add-attr" + + verifySingleSpan(t, tc, nodeToExclude, spanToInclude, func(span pdata.Span) { + // Verify attributes was not added to the new internal data span. + assert.Equal(t, span.Attributes().Len(), 0) + }) + + // Create another span that does not match "include" filter. + spanToExclude := "span-not-to-add-attr" + verifySingleSpan(t, tc, nodeToInclude, spanToExclude, func(span pdata.Span) { + // Verify attributes was not added to the new internal data span. + assert.Equal(t, span.Attributes().Len(), 0) + }) + }) + } +} diff --git a/internal/otel_collector/testbed/tests/utils.go b/internal/otel_collector/testbed/tests/utils.go new file mode 100644 index 00000000000..472659fdd5e --- /dev/null +++ b/internal/otel_collector/testbed/tests/utils.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +import "go.opentelemetry.io/collector/consumer/pdata" + +func attributesToMap(attributes pdata.AttributeMap) map[string]pdata.AttributeValue { + out := map[string]pdata.AttributeValue{} + attributes.ForEach(func(k string, v pdata.AttributeValue) { + out[k] = v + }) + return out +} diff --git a/internal/otel_collector/testutil/logstest/logs.go b/internal/otel_collector/testutil/logstest/logs.go new file mode 100644 index 00000000000..962e2e6214e --- /dev/null +++ b/internal/otel_collector/testutil/logstest/logs.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logstest + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +type Log struct { + Timestamp int64 + Body pdata.AttributeValue + Attributes map[string]pdata.AttributeValue +} + +// A convenience function for constructing logs for tests in a way that is +// relatively easy to read and write declaratively compared to the highly +// imperative and verbose method of using pdata directly. +// Attributes are sorted by key name. +func Logs(recs ...Log) pdata.Logs { + out := pdata.NewLogs() + + logs := out.ResourceLogs() + + logs.Resize(1) + rls := logs.At(0) + + rls.InstrumentationLibraryLogs().Resize(1) + logSlice := rls.InstrumentationLibraryLogs().At(0).Logs() + + logSlice.Resize(len(recs)) + for i := range recs { + l := logSlice.At(i) + recs[i].Body.CopyTo(l.Body()) + l.SetTimestamp(pdata.TimestampUnixNano(recs[i].Timestamp)) + l.Attributes().InitFromMap(recs[i].Attributes) + l.Attributes().Sort() + } + + return out +} diff --git a/internal/otel_collector/testutil/logstest/logs_test.go b/internal/otel_collector/testutil/logstest/logs_test.go new file mode 100644 index 00000000000..5c20b398efa --- /dev/null +++ b/internal/otel_collector/testutil/logstest/logs_test.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logstest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestLogs(t *testing.T) { + logs := Logs(Log{ + Timestamp: 1, + Body: pdata.NewAttributeValueString("asdf"), + Attributes: map[string]pdata.AttributeValue{ + "a": pdata.NewAttributeValueString("b"), + }, + }) + + require.Equal(t, 1, logs.LogRecordCount()) +} diff --git a/internal/otel_collector/testutil/metricstestutil/metricsutil.go b/internal/otel_collector/testutil/metricstestutil/metricsutil.go new file mode 100644 index 00000000000..5b9c3354915 --- /dev/null +++ b/internal/otel_collector/testutil/metricstestutil/metricsutil.go @@ -0,0 +1,204 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metricstestutil + +import ( + "time" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + timestamppb "github.com/golang/protobuf/ptypes/timestamp" + wrapperspb "github.com/golang/protobuf/ptypes/wrappers" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Gauge creates a gauge metric. +func Gauge(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_GAUGE_DOUBLE, name, keys, timeseries) +} + +// GaugeInt creates a gauge metric of type int64. +func GaugeInt(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_GAUGE_INT64, name, keys, timeseries) +} + +// GaugeDist creates a gauge distribution metric. +func GaugeDist(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_GAUGE_DISTRIBUTION, name, keys, timeseries) +} + +// Cumulative creates a cumulative metric. +func Cumulative(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, name, keys, timeseries) +} + +// CumulativeInt creates a cumulative metric of type int64. +func CumulativeInt(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_CUMULATIVE_INT64, name, keys, timeseries) +} + +// CumulativeDist creates a cumulative distribution metric. +func CumulativeDist(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, name, keys, timeseries) +} + +// Summary creates a summary metric. +func Summary(name string, keys []string, timeseries ...*metricspb.TimeSeries) *metricspb.Metric { + return metric(metricspb.MetricDescriptor_SUMMARY, name, keys, timeseries) +} + +// Timeseries creates a timeseries. It takes the start time stamp, a sequence of label values (associated +// with the label keys in the overall metric), and the value of the timeseries. +func Timeseries(sts time.Time, vals []string, point *metricspb.Point) *metricspb.TimeSeries { + return &metricspb.TimeSeries{ + StartTimestamp: Timestamp(sts), + Points: []*metricspb.Point{point}, + LabelValues: toVals(vals), + } +} + +// Double creates a double point. +func Double(ts time.Time, value float64) *metricspb.Point { + return &metricspb.Point{Timestamp: Timestamp(ts), Value: &metricspb.Point_DoubleValue{DoubleValue: value}} +} + +// DistPt creates a distribution point. It takes the time stamp, the bucket boundaries for the distribution, and +// the and counts for the individual buckets as input. +func DistPt(ts time.Time, bounds []float64, counts []int64) *metricspb.Point { + var count int64 + var sum float64 + buckets := make([]*metricspb.DistributionValue_Bucket, len(counts)) + + for i, bcount := range counts { + count += bcount + buckets[i] = &metricspb.DistributionValue_Bucket{Count: bcount} + // create a sum based on lower bucket bounds + // e.g. for bounds = {0.1, 0.2, 0.4} and counts = {2, 3, 7, 9) + // sum = 0*2 + 0.1*3 + 0.2*7 + 0.4*9 + if i > 0 { + sum += float64(bcount) * bounds[i-1] + } + } + distrValue := &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: bounds, + }, + }, + }, + Count: count, + Sum: sum, + Buckets: buckets, + // There's no way to compute SumOfSquaredDeviation from prometheus data + } + return &metricspb.Point{Timestamp: Timestamp(ts), Value: &metricspb.Point_DistributionValue{DistributionValue: distrValue}} +} + +// SummPt creates a summary point. +func SummPt(ts time.Time, count int64, sum float64, percent, vals []float64) *metricspb.Point { + percentiles := make([]*metricspb.SummaryValue_Snapshot_ValueAtPercentile, len(percent)) + for i := 0; i < len(percent); i++ { + percentiles[i] = &metricspb.SummaryValue_Snapshot_ValueAtPercentile{Percentile: percent[i], Value: vals[i]} + } + summaryValue := &metricspb.SummaryValue{ + Sum: &wrapperspb.DoubleValue{Value: sum}, + Count: &wrapperspb.Int64Value{Value: count}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: percentiles, + }, + } + return &metricspb.Point{Timestamp: Timestamp(ts), Value: &metricspb.Point_SummaryValue{SummaryValue: summaryValue}} +} + +// Timestamp creates a timestamp. +func Timestamp(ts time.Time) *timestamppb.Timestamp { + return ×tamppb.Timestamp{ + Seconds: ts.Unix(), + Nanos: int32(ts.Nanosecond()), + } +} + +func metric(ty metricspb.MetricDescriptor_Type, name string, keys []string, timeseries []*metricspb.TimeSeries) *metricspb.Metric { + return &metricspb.Metric{ + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: name, + Description: "metrics description", + Unit: "", + Type: ty, + LabelKeys: toKeys(keys), + }, + Timeseries: timeseries, + } +} + +func toKeys(keys []string) []*metricspb.LabelKey { + res := make([]*metricspb.LabelKey, 0, len(keys)) + for _, key := range keys { + res = append(res, &metricspb.LabelKey{Key: key, Description: "description: " + key}) + } + return res +} + +func toVals(vals []string) []*metricspb.LabelValue { + res := make([]*metricspb.LabelValue, 0, len(vals)) + for _, val := range vals { + res = append(res, &metricspb.LabelValue{Value: val, HasValue: true}) + } + return res +} + +// SortedMetrics is mainly useful for tests. It gets all of the attributes and +// labels in sorted order so they can be consistently tested. +func SortedMetrics(metrics pdata.Metrics) pdata.Metrics { + for i := 0; i < metrics.ResourceMetrics().Len(); i++ { + rm := metrics.ResourceMetrics().At(i) + rm.Resource().Attributes().Sort() + + for j := 0; j < rm.InstrumentationLibraryMetrics().Len(); j++ { + ilm := rm.InstrumentationLibraryMetrics().At(j) + for k := 0; k < ilm.Metrics().Len(); k++ { + m := ilm.Metrics().At(k) + switch m.DataType() { + case pdata.MetricDataTypeIntGauge: + for l := 0; l < m.IntGauge().DataPoints().Len(); l++ { + m.IntGauge().DataPoints().At(l).LabelsMap().Sort() + } + case pdata.MetricDataTypeIntSum: + for l := 0; l < m.IntSum().DataPoints().Len(); l++ { + m.IntSum().DataPoints().At(l).LabelsMap().Sort() + } + case pdata.MetricDataTypeDoubleGauge: + for l := 0; l < m.DoubleGauge().DataPoints().Len(); l++ { + m.DoubleGauge().DataPoints().At(l).LabelsMap().Sort() + } + case pdata.MetricDataTypeDoubleSum: + for l := 0; l < m.DoubleSum().DataPoints().Len(); l++ { + m.DoubleSum().DataPoints().At(l).LabelsMap().Sort() + } + case pdata.MetricDataTypeIntHistogram: + for l := 0; l < m.IntHistogram().DataPoints().Len(); l++ { + m.IntHistogram().DataPoints().At(l).LabelsMap().Sort() + } + case pdata.MetricDataTypeDoubleHistogram: + for l := 0; l < m.DoubleHistogram().DataPoints().Len(); l++ { + m.DoubleHistogram().DataPoints().At(l).LabelsMap().Sort() + } + } + } + } + } + return metrics +} diff --git a/internal/otel_collector/testutil/metricstestutil/metricsutil_test.go b/internal/otel_collector/testutil/metricstestutil/metricsutil_test.go new file mode 100644 index 00000000000..abe7a0cedb2 --- /dev/null +++ b/internal/otel_collector/testutil/metricstestutil/metricsutil_test.go @@ -0,0 +1,180 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metricstestutil + +import ( + "testing" + "time" + + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestResourceProcessor(t *testing.T) { + op1 := "op1" + op2 := "op2" + k1k2 := []string{"k1", "k2"} + v1v2 := []string{"v1", "v2"} + v10v20 := []string{"v10", "v20"} + bounds0 := []float64{1} + percent0 := []float64{10} + t1Ms := time.Unix(0, 1000000) + t3Ms := time.Unix(0, 3000000) + t5Ms := time.Unix(0, 5000000) + + k1k2Labels := []*metricspb.LabelKey{ + {Key: "k1", Description: "description: k1"}, + {Key: "k2", Description: "description: k2"}, + } + + v1v2Values := []*metricspb.LabelValue{ + {Value: "v1", HasValue: true}, + {Value: "v2", HasValue: true}, + } + + v10v20Values := []*metricspb.LabelValue{ + {Value: "v10", HasValue: true}, + {Value: "v20", HasValue: true}, + } + + ts1Ms := ×tamppb.Timestamp{Seconds: 0, Nanos: 1000000} + ts3Ms := ×tamppb.Timestamp{Seconds: 0, Nanos: 3000000} + ts5Ms := ×tamppb.Timestamp{Seconds: 0, Nanos: 5000000} + + d44 := &metricspb.Point_DoubleValue{DoubleValue: 44} + d65 := &metricspb.Point_DoubleValue{DoubleValue: 65} + d90 := &metricspb.Point_DoubleValue{DoubleValue: 90} + + dist := &metricspb.Point_DistributionValue{ + DistributionValue: &metricspb.DistributionValue{ + BucketOptions: &metricspb.DistributionValue_BucketOptions{ + Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ + Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{1}, + }, + }, + }, + Count: 2, + Sum: 0, + Buckets: []*metricspb.DistributionValue_Bucket{{Count: 2}}, + }, + } + + summ := &metricspb.Point_SummaryValue{ + SummaryValue: &metricspb.SummaryValue{ + Sum: &wrapperspb.DoubleValue{Value: 40}, + Count: &wrapperspb.Int64Value{Value: 10}, + Snapshot: &metricspb.SummaryValue_Snapshot{ + PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ + {Percentile: 10, Value: 1}, + }, + }, + }, + } + + got := []*metricspb.Metric{ + Gauge(op1, k1k2, Timeseries(t1Ms, v1v2, Double(t1Ms, 44))), + GaugeDist(op2, k1k2, Timeseries(t3Ms, v1v2, DistPt(t1Ms, bounds0, []int64{2}))), + Cumulative(op1, k1k2, Timeseries(t5Ms, v1v2, Double(t5Ms, 90)), Timeseries(t5Ms, v10v20, Double(t5Ms, 65))), + CumulativeDist(op2, k1k2, Timeseries(t1Ms, v1v2, DistPt(t1Ms, bounds0, []int64{2}))), + Summary(op1, k1k2, Timeseries(t1Ms, v1v2, SummPt(t1Ms, 10, 40, percent0, []float64{1, 5}))), + } + + want := []*metricspb.Metric{ + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: op1, + Description: "metrics description", + Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, + LabelKeys: k1k2Labels, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1Ms, + LabelValues: v1v2Values, + Points: []*metricspb.Point{{Timestamp: ts1Ms, Value: d44}}, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: op2, + Description: "metrics description", + Type: metricspb.MetricDescriptor_GAUGE_DISTRIBUTION, + LabelKeys: k1k2Labels, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts3Ms, + LabelValues: v1v2Values, + Points: []*metricspb.Point{{Timestamp: ts1Ms, Value: dist}}, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: op1, + Description: "metrics description", + Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: k1k2Labels, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts5Ms, + LabelValues: v1v2Values, + Points: []*metricspb.Point{{Timestamp: ts5Ms, Value: d90}}, + }, + { + StartTimestamp: ts5Ms, + LabelValues: v10v20Values, + Points: []*metricspb.Point{{Timestamp: ts5Ms, Value: d65}}, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: op2, + Description: "metrics description", + Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: k1k2Labels, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1Ms, + LabelValues: v1v2Values, + Points: []*metricspb.Point{{Timestamp: ts1Ms, Value: dist}}, + }, + }, + }, + { + MetricDescriptor: &metricspb.MetricDescriptor{ + Name: op1, + Description: "metrics description", + Type: metricspb.MetricDescriptor_SUMMARY, + LabelKeys: k1k2Labels, + }, + Timeseries: []*metricspb.TimeSeries{ + { + StartTimestamp: ts1Ms, + LabelValues: v1v2Values, + Points: []*metricspb.Point{{Timestamp: ts1Ms, Value: summ}}, + }, + }, + }, + } + assert.Equalf(t, want, got, "got %v, want %v", got, want) +} diff --git a/internal/otel_collector/testutil/testutil.go b/internal/otel_collector/testutil/testutil.go new file mode 100644 index 00000000000..b2f5f33ba62 --- /dev/null +++ b/internal/otel_collector/testutil/testutil.go @@ -0,0 +1,235 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testutil + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type portpair struct { + first string + last string +} + +// GenerateNormalizedJSON generates a normalized JSON from the string +// given to the function. Useful to compare JSON contents that +// may have differences due to formatting. It returns nil in case of +// invalid JSON. +func GenerateNormalizedJSON(t *testing.T, jsonStr string) string { + var i interface{} + + err := json.Unmarshal([]byte(jsonStr), &i) + require.NoError(t, err) + + n, err := json.Marshal(i) + require.NoError(t, err) + + return string(n) +} + +func TempSocketName(t *testing.T) string { + tmpfile, err := ioutil.TempFile("", "sock") + require.NoError(t, err) + require.NoError(t, tmpfile.Close()) + socket := tmpfile.Name() + require.NoError(t, os.Remove(socket)) + return socket +} + +// GetAvailableLocalAddress finds an available local port and returns an endpoint +// describing it. The port is available for opening when this function returns +// provided that there is no race by some other code to grab the same port +// immediately. +func GetAvailableLocalAddress(t *testing.T) string { + ln, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err, "Failed to get a free local port") + // There is a possible race if something else takes this same port before + // the test uses it, however, that is unlikely in practice. + defer ln.Close() + return ln.Addr().String() +} + +// GetAvailablePort finds an available local port and returns it. The port is +// available for opening when this function returns provided that there is no +// race by some other code to grab the same port immediately. +func GetAvailablePort(t *testing.T) uint16 { + // Retry has been added for windows as net.Listen can return a port that is not actually available. Details can be + // found in https://github.com/docker/for-win/issues/3171 but to summarize Hyper-V will reserve ranges of ports + // which do not show up under the "netstat -ano" but can only be found by + // "netsh interface ipv4 show excludedportrange protocol=tcp". We'll use []exclusions to hold those ranges and + // retry if the port returned by GetAvailableLocalAddress falls in one of those them. + var exclusions []portpair + portFound := false + var port string + var err error + if runtime.GOOS == "windows" { + exclusions = getExclusionsList(t) + } + + for !portFound { + endpoint := GetAvailableLocalAddress(t) + _, port, err = net.SplitHostPort(endpoint) + require.NoError(t, err) + portFound = true + if runtime.GOOS == "windows" { + for _, pair := range exclusions { + if port >= pair.first && port <= pair.last { + portFound = false + break + } + } + } + } + + portInt, err := strconv.Atoi(port) + require.NoError(t, err) + + return uint16(portInt) +} + +// Get excluded ports on Windows from the command: netsh interface ipv4 show excludedportrange protocol=tcp +func getExclusionsList(t *testing.T) []portpair { + cmd := exec.Command("netsh", "interface", "ipv4", "show", "excludedportrange", "protocol=tcp") + output, err := cmd.CombinedOutput() + require.NoError(t, err) + + exclusions := createExclusionsList(string(output), t) + return exclusions +} + +func createExclusionsList(exclusionsText string, t *testing.T) []portpair { + exclusions := []portpair{} + + parts := strings.Split(exclusionsText, "--------") + require.Equal(t, len(parts), 3) + portsText := strings.Split(parts[2], "*") + require.Equal(t, len(portsText), 2) + lines := strings.Split(portsText[0], "\n") + for _, line := range lines { + if strings.TrimSpace(line) != "" { + entries := strings.Fields(strings.TrimSpace(line)) + require.Equal(t, len(entries), 2) + pair := portpair{entries[0], entries[1]} + exclusions = append(exclusions, pair) + } + } + return exclusions +} + +// WaitForPort repeatedly attempts to open a local port until it either succeeds or 5 seconds pass +// It is useful if you need to asynchronously start a service and wait for it to start +func WaitForPort(t *testing.T, port uint16) error { + t.Helper() + + totalDuration := 5 * time.Second + wait := 100 * time.Millisecond + address := fmt.Sprintf("localhost:%d", port) + + ticker := time.NewTicker(wait) + defer ticker.Stop() + + timeout := time.After(totalDuration) + + for { + select { + case <-ticker.C: + conn, err := net.Dial("tcp", address) + if err == nil && conn != nil { + conn.Close() + return nil + } + + case <-timeout: + return fmt.Errorf("failed to wait for port %d", port) + } + } +} + +// HostPortFromAddr extracts host and port from a network address +func HostPortFromAddr(addr net.Addr) (host string, port int, err error) { + addrStr := addr.String() + sepIndex := strings.LastIndex(addrStr, ":") + if sepIndex < 0 { + return "", -1, errors.New("failed to parse host:port") + } + host, portStr := addrStr[:sepIndex], addrStr[sepIndex+1:] + port, err = strconv.Atoi(portStr) + return host, port, err +} + +// WaitFor the specific condition for up to 10 seconds. Records a test error +// if condition does not become true. +func WaitFor(t *testing.T, cond func() bool, errMsg ...interface{}) bool { + t.Helper() + + startTime := time.Now() + + // Start with 5 ms waiting interval between condition re-evaluation. + waitInterval := time.Millisecond * 5 + + for { + time.Sleep(waitInterval) + + // Increase waiting interval exponentially up to 500 ms. + if waitInterval < time.Millisecond*500 { + waitInterval *= 2 + } + + if cond() { + return true + } + + if time.Since(startTime) > time.Second*10 { + // Waited too long + t.Error("Time out waiting for", errMsg) + return false + } + } +} + +// LimitedWriter is an io.Writer that will return an EOF error after MaxLen has +// been reached. If MaxLen is 0, Writes will always succeed. +type LimitedWriter struct { + bytes.Buffer + MaxLen int +} + +var _ io.Writer = new(LimitedWriter) + +func (lw *LimitedWriter) Write(p []byte) (n int, err error) { + if lw.MaxLen != 0 && len(p)+lw.Len() > lw.MaxLen { + return 0, io.EOF + } + return lw.Buffer.Write(p) +} + +func (lw *LimitedWriter) Close() error { + return nil +} diff --git a/internal/otel_collector/testutil/testutil_test.go b/internal/otel_collector/testutil/testutil_test.go new file mode 100644 index 00000000000..08f9514eb1f --- /dev/null +++ b/internal/otel_collector/testutil/testutil_test.go @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testutil + +import ( + "fmt" + "net" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetAvailableLocalAddress(t *testing.T) { + testEndpointAvailable(t, GetAvailableLocalAddress(t)) +} + +func TestGetAvailablePort(t *testing.T) { + portStr := strconv.Itoa(int(GetAvailablePort(t))) + require.NotEqual(t, "", portStr) + + testEndpointAvailable(t, "localhost:"+portStr) +} + +func TestWaitForPort(t *testing.T) { + port := GetAvailablePort(t) + err := WaitForPort(t, port) + require.Error(t, err) + + port = GetAvailablePort(t) + l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) + require.NoError(t, err) + + err = WaitForPort(t, port) + require.NoError(t, err) + + err = l.Close() + require.NoError(t, err) +} + +func testEndpointAvailable(t *testing.T, endpoint string) { + // Endpoint should be free. + ln0, err := net.Listen("tcp", endpoint) + require.NoError(t, err) + require.NotNil(t, ln0) + defer ln0.Close() + + // Ensure that the endpoint wasn't something like ":0" by checking that a + // second listener will fail. + ln1, err := net.Listen("tcp", endpoint) + require.Error(t, err) + require.Nil(t, ln1) +} + +func TestCreateExclusionsList(t *testing.T) { + // Test two examples of typical output from "netsh interface ipv4 show excludedportrange protocol=tcp" + emptyExclusionsText := ` + +Protocol tcp Port Exclusion Ranges + +Start Port End Port +---------- -------- + +* - Administered port exclusions.` + + exclusionsText := ` + +Start Port End Port +---------- -------- + 49697 49796 + 49797 49896 + +* - Administered port exclusions. +` + exclusions := createExclusionsList(exclusionsText, t) + require.Equal(t, len(exclusions), 2) + + emptyExclusions := createExclusionsList(emptyExclusionsText, t) + require.Equal(t, len(emptyExclusions), 0) +} diff --git a/internal/otel_collector/translator/conventions/opencensus.go b/internal/otel_collector/translator/conventions/opencensus.go new file mode 100644 index 00000000000..727af4625fd --- /dev/null +++ b/internal/otel_collector/translator/conventions/opencensus.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package conventions + +// OTLP attributes to map certain OpenCensus proto fields. These fields don't have +// corresponding fields in OTLP, nor are defined in OTLP semantic conventions. +// TODO: decide if any of these must be in OTLP semantic conventions. +const ( + OCAttributeProcessStartTime = "opencensus.starttime" + OCAttributeProcessID = "opencensus.pid" + OCAttributeExporterVersion = "opencensus.exporterversion" + OCAttributeResourceType = "opencensus.resourcetype" + OCAttributeSameProcessAsParentSpan = "opencensus.same_process_as_parent_span" + OCTimeEventMessageEventType = "opencensus.timeevent.messageevent.type" + OCTimeEventMessageEventID = "opencensus.timeevent.messageevent.id" + OCTimeEventMessageEventUSize = "opencensus.timeevent.messageevent.usize" + OCTimeEventMessageEventCSize = "opencensus.timeevent.messageevent.csize" +) diff --git a/internal/otel_collector/translator/conventions/opentelemetry.go b/internal/otel_collector/translator/conventions/opentelemetry.go new file mode 100644 index 00000000000..3f3b81cdf55 --- /dev/null +++ b/internal/otel_collector/translator/conventions/opentelemetry.go @@ -0,0 +1,278 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package conventions + +// OpenTelemetry Semantic Convention values for Resource attribute names. +// See: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions/README.md +const ( + AttributeCloudAccount = "cloud.account.id" + AttributeCloudProvider = "cloud.provider" + AttributeCloudRegion = "cloud.region" + AttributeCloudZone = "cloud.zone" + AttributeContainerID = "container.id" + AttributeContainerImage = "container.image.name" + AttributeContainerName = "container.name" + AttributeContainerTag = "container.image.tag" + AttributeDeploymentEnvironment = "deployment.environment" + AttributeFaasID = "faas.id" + AttributeFaasInstance = "faas.instance" + AttributeFaasName = "faas.name" + AttributeFaasVersion = "faas.version" + AttributeHostID = "host.id" + AttributeHostImageID = "host.image.id" + AttributeHostImageName = "host.image.name" + AttributeHostImageVersion = "host.image.version" + AttributeHostName = "host.name" + AttributeHostType = "host.type" + AttributeK8sCluster = "k8s.cluster.name" + AttributeK8sContainer = "k8s.container.name" + AttributeK8sCronJob = "k8s.cronjob.name" + AttributeK8sCronJobUID = "k8s.cronjob.uid" + AttributeK8sDaemonSet = "k8s.daemonset.name" + AttributeK8sDaemonSetUID = "k8s.daemonset.uid" + AttributeK8sDeployment = "k8s.deployment.name" + AttributeK8sDeploymentUID = "k8s.deployment.uid" + AttributeK8sJob = "k8s.job.name" + AttributeK8sJobUID = "k8s.job.uid" + AttributeK8sNamespace = "k8s.namespace.name" + AttributeK8sPod = "k8s.pod.name" + AttributeK8sPodUID = "k8s.pod.uid" + AttributeK8sReplicaSet = "k8s.replicaset.name" + AttributeK8sReplicaSetUID = "k8s.replicaset.uid" + AttributeK8sStatefulSet = "k8s.statefulset.name" + AttributeK8sStatefulSetUID = "k8s.statefulset.uid" + AttributeOSType = "os.type" + AttributeOSDescription = "os.description" + AttributeProcessCommand = "process.command" + AttributeProcessCommandLine = "process.command_line" + AttributeProcessExecutableName = "process.executable.name" + AttributeProcessExecutablePath = "process.executable.path" + AttributeProcessID = "process.pid" + AttributeProcessOwner = "process.owner" + AttributeServiceInstance = "service.instance.id" + AttributeServiceName = "service.name" + AttributeServiceNamespace = "service.namespace" + AttributeServiceVersion = "service.version" + AttributeTelemetryAutoVersion = "telemetry.auto.version" + AttributeTelemetrySDKLanguage = "telemetry.sdk.language" + AttributeTelemetrySDKName = "telemetry.sdk.name" + AttributeTelemetrySDKVersion = "telemetry.sdk.version" +) + +// OpenTelemetry Semantic Convention values for Resource attribute "telemetry.sdk.language" values. +// See: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions/README.md +const ( + AttributeSDKLangValueCPP = "cpp" + AttributeSDKLangValueDotNET = "dotnet" + AttributeSDKLangValueErlang = "erlang" + AttributeSDKLangValueGo = "go" + AttributeSDKLangValueJava = "java" + AttributeSDKLangValueNodeJS = "nodejs" + AttributeSDKLangValuePHP = "php" + AttributeSDKLangValuePython = "python" + AttributeSDKLangValueRuby = "ruby" + AttributeSDKLangValueWebJS = "webjs" +) + +// OpenTelemetry Semantic Convention values for Resource attribute "cloud.provider" values. +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/cloud.md +const ( + AttributeCloudProviderAWS = "aws" + AttributeCloudProviderAzure = "azure" + AttributeCloudProviderGCP = "gcp" +) + +// GetResourceSemanticConventionAttributeNames a slice with all the Resource Semantic Conventions attribute names. +func GetResourceSemanticConventionAttributeNames() []string { + return []string{ + AttributeCloudAccount, + AttributeCloudProvider, + AttributeCloudRegion, + AttributeCloudZone, + AttributeContainerID, + AttributeContainerImage, + AttributeContainerName, + AttributeContainerTag, + AttributeDeploymentEnvironment, + AttributeFaasID, + AttributeFaasInstance, + AttributeFaasName, + AttributeFaasVersion, + AttributeHostID, + AttributeHostImageID, + AttributeHostImageName, + AttributeHostImageVersion, + AttributeHostName, + AttributeHostType, + AttributeK8sCluster, + AttributeK8sContainer, + AttributeK8sCronJob, + AttributeK8sCronJobUID, + AttributeK8sDaemonSet, + AttributeK8sDaemonSetUID, + AttributeK8sDeployment, + AttributeK8sDeploymentUID, + AttributeK8sJob, + AttributeK8sJobUID, + AttributeK8sNamespace, + AttributeK8sPod, + AttributeK8sPodUID, + AttributeK8sReplicaSet, + AttributeK8sReplicaSetUID, + AttributeK8sStatefulSet, + AttributeK8sStatefulSetUID, + AttributeOSType, + AttributeOSDescription, + AttributeProcessCommand, + AttributeProcessCommandLine, + AttributeProcessExecutableName, + AttributeProcessExecutablePath, + AttributeProcessID, + AttributeProcessOwner, + AttributeServiceInstance, + AttributeServiceName, + AttributeServiceNamespace, + AttributeServiceVersion, + AttributeTelemetryAutoVersion, + AttributeTelemetrySDKLanguage, + AttributeTelemetrySDKName, + AttributeTelemetrySDKVersion, + } +} + +// OpenTelemetry Semantic Convention values for general Span attribute names. +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/span-general.md +const ( + AttributeComponent = "component" + AttributeEnduserID = "enduser.id" + AttributeEnduserRole = "enduser.role" + AttributeEnduserScope = "enduser.scope" + AttributeNetHostIP = "net.host.ip" + AttributeNetHostName = "net.host.name" + AttributeNetHostPort = "net.host.port" + AttributeNetPeerIP = "net.peer.ip" + AttributeNetPeerName = "net.peer.name" + AttributeNetPeerPort = "net.peer.port" + AttributeNetTransport = "net.transport" + AttributePeerService = "peer.service" +) + +// OpenTelemetry Semantic Convention values for component attribute values. +// Possibly being removed due to issue #336 +const ( + ComponentTypeHTTP = "http" + ComponentTypeGRPC = "grpc" +) + +// OpenTelemetry Semantic Convention attribute names for HTTP related attributes +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md +const ( + AttributeHTTPClientIP = "http.client_ip" + AttributeHTTPFlavor = "http.flavor" + AttributeHTTPHost = "http.host" + AttributeHTTPHostName = "host.name" + AttributeHTTPHostPort = "host.port" + AttributeHTTPMethod = "http.method" + AttributeHTTPRequestContentLength = "http.request_content_length" + AttributeHTTPRequestContentLengthUncompressed = "http.request_content_length_uncompressed" + AttributeHTTPResponseContentLength = "http.response_content_length" + AttributeHTTPResponseContentLengthUncompressed = "http.response_content_length_uncompressed" + AttributeHTTPRoute = "http.route" + AttributeHTTPScheme = "http.scheme" + AttributeHTTPServerName = "http.server_name" + AttributeHTTPStatusCode = "http.status_code" + AttributeHTTPStatusText = "http.status_text" + AttributeHTTPTarget = "http.target" + AttributeHTTPURL = "http.url" + AttributeHTTPUserAgent = "http.user_agent" +) + +// OpenTelemetry Semantic Convention attribute names for database related attributes +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md +const ( + AttributeDBConnectionString = "db.connection_string" + + AttributeDBCassandraKeyspace = "db.cassandra.keyspace" + AttributeDBHBaseNamespace = "db.hbase.namespace" + AttributeDBJDBCDriverClassname = "db.jdbc.driver_classname" + AttributeDBMongoDBCollection = "db.mongodb.collection" + AttributeDBMsSQLInstanceName = "db.mssql.instance_name" + + AttributeDBName = "db.name" + AttributeDBOperation = "db.operation" + AttributeDBRedisDatabaseIndex = "db.redis.database_index" + AttributeDBStatement = "db.statement" + AttributeDBSystem = "db.system" + AttributeDBUser = "db.user" +) + +// OpenTelemetry Semantic Convention attribute names for gRPC related attributes +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md +const ( + AttributeMessageCompressedSize = "message.compressed_size" + AttributeMessageID = "message.id" + AttributeMessageType = "message.type" + AttributeMessageUncompressedSize = "message.uncompressed_size" + AttributeRPCMethod = "rpc.method" + AttributeRPCService = "rpc.service" + AttributeRPCSystem = "rpc.system" + EventTypeMessage = "message" + MessageTypeReceived = "RECEIVED" + MessageTypeSent = "SENT" +) + +// OpenTelemetry Semantic Convention attribute names for FaaS related attributes +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/faas.md +const ( + AttributeFaaSCron = "faas.cron" + AttributeFaaSDocumentCollection = "faas.document.collection" + AttributeFaaSDocumentName = "faas.document.name" + AttributeFaaSDocumentOperation = "faas.document.operation" + AttributeFaaSDocumentTime = "faas.document.time" + AttributeFaaSExecution = "faas.execution" + AttributeFaaSTime = "faas.time" + AttributeFaaSTrigger = "faas.trigger" + FaaSTriggerDataSource = "datasource" + FaaSTriggerHTTP = "http" + FaaSTriggerOther = "other" + FaaSTriggerPubSub = "pubsub" + FaaSTriggerTimer = "timer" +) + +// OpenTelemetry Semantic Convention attribute names for messaging system related attributes +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md +const ( + AttributeMessagingConversationID = "messaging.conversation_id" + AttributeMessagingDestination = "messaging.destination" + AttributeMessagingDestinationKind = "messaging.destination_kind" + AttributeMessagingMessageID = "messaging.message_id" + AttributeMessagingOperation = "messaging.operation" + AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes" + AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes" + AttributeMessagingProtocol = "messaging.protocol" + AttributeMessagingProtocolVersion = "messaging.protocol_version" + AttributeMessagingSystem = "messaging.system" + AttributeMessagingTempDestination = "messaging.temp_destination" + AttributeMessagingURL = "messaging.url" +) + +// OpenTelemetry Semantic Convention attribute names for exceptions +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/exceptions.md +const ( + AttributeExceptionEventName = "exception" + AttributeExceptionMessage = "exception.message" + AttributeExceptionStacktrace = "exception.stacktrace" + AttributeExceptionType = "exception.type" +) diff --git a/internal/otel_collector/translator/internaldata/metrics_to_oc.go b/internal/otel_collector/translator/internaldata/metrics_to_oc.go new file mode 100644 index 00000000000..612d5de048b --- /dev/null +++ b/internal/otel_collector/translator/internaldata/metrics_to_oc.go @@ -0,0 +1,523 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "sort" + + ocmetrics "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + "github.com/golang/protobuf/ptypes/wrappers" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" +) + +type labelKeys struct { + // ordered OC label keys + keys []*ocmetrics.LabelKey + // map from a label key literal + // to its index in the slice above + keyIndices map[string]int +} + +// MetricsToOC may be used only by OpenCensus receiver and exporter implementations. +// TODO: move this function to OpenCensus package. +func MetricsToOC(md pdata.Metrics) []consumerdata.MetricsData { + resourceMetrics := md.ResourceMetrics() + + if resourceMetrics.Len() == 0 { + return nil + } + + ocResourceMetricsList := make([]consumerdata.MetricsData, 0, resourceMetrics.Len()) + for i := 0; i < resourceMetrics.Len(); i++ { + ocResourceMetricsList = append(ocResourceMetricsList, resourceMetricsToOC(resourceMetrics.At(i))) + } + + return ocResourceMetricsList +} + +func resourceMetricsToOC(rm pdata.ResourceMetrics) consumerdata.MetricsData { + ocMetricsData := consumerdata.MetricsData{} + ocMetricsData.Node, ocMetricsData.Resource = internalResourceToOC(rm.Resource()) + ilms := rm.InstrumentationLibraryMetrics() + if ilms.Len() == 0 { + return ocMetricsData + } + // Approximate the number of the metrics as the number of the metrics in the first + // instrumentation library info. + ocMetrics := make([]*ocmetrics.Metric, 0, ilms.At(0).Metrics().Len()) + for i := 0; i < ilms.Len(); i++ { + ilm := ilms.At(i) + // TODO: Handle instrumentation library name and version. + metrics := ilm.Metrics() + for j := 0; j < metrics.Len(); j++ { + ocMetrics = append(ocMetrics, metricToOC(metrics.At(j))) + } + } + if len(ocMetrics) != 0 { + ocMetricsData.Metrics = ocMetrics + } + return ocMetricsData +} + +func metricToOC(metric pdata.Metric) *ocmetrics.Metric { + labelKeys := collectLabelKeys(metric) + return &ocmetrics.Metric{ + MetricDescriptor: descriptorToOC(metric, labelKeys), + Timeseries: dataPointsToTimeseries(metric, labelKeys), + Resource: nil, + } +} + +func collectLabelKeys(metric pdata.Metric) *labelKeys { + // NOTE: Internal data structure and OpenCensus have different representations of labels: + // - OC has a single "global" ordered list of label keys per metric in the MetricDescriptor; + // then, every data point has an ordered list of label values matching the key index. + // - Internally labels are stored independently as key-value storage for each point. + // + // So what we do in this translator: + // - Scan all points and their labels to find all label keys used across the metric, + // sort them and set in the MetricDescriptor. + // - For each point we generate an ordered list of label values, + // matching the order of label keys returned here (see `labelValuesToOC` function). + // - If the value for particular label key is missing in the point, we set it to default + // to preserve 1:1 matching between label keys and values. + + // First, collect a set of all labels present in the metric + keySet := make(map[string]struct{}) + + switch metric.DataType() { + case pdata.MetricDataTypeIntGauge: + collectLabelKeysIntDataPoints(metric.IntGauge().DataPoints(), keySet) + case pdata.MetricDataTypeDoubleGauge: + collectLabelKeysDoubleDataPoints(metric.DoubleGauge().DataPoints(), keySet) + case pdata.MetricDataTypeIntSum: + collectLabelKeysIntDataPoints(metric.IntSum().DataPoints(), keySet) + case pdata.MetricDataTypeDoubleSum: + collectLabelKeysDoubleDataPoints(metric.DoubleSum().DataPoints(), keySet) + case pdata.MetricDataTypeIntHistogram: + collectLabelKeysIntHistogramDataPoints(metric.IntHistogram().DataPoints(), keySet) + case pdata.MetricDataTypeDoubleHistogram: + collectLabelKeysDoubleHistogramDataPoints(metric.DoubleHistogram().DataPoints(), keySet) + case pdata.MetricDataTypeDoubleSummary: + collectLabelKeysDoubleSummaryDataPoints(metric.DoubleSummary().DataPoints(), keySet) + } + + if len(keySet) == 0 { + return &labelKeys{} + } + + // Sort keys: while not mandatory, this helps to make the + // output OC metric deterministic and easy to test, i.e. + // the same set of labels will always produce + // OC labels in the alphabetically sorted order. + sortedKeys := make([]string, 0, len(keySet)) + for key := range keySet { + sortedKeys = append(sortedKeys, key) + } + sort.Strings(sortedKeys) + + // Construct a resulting list of label keys + keys := make([]*ocmetrics.LabelKey, 0, len(sortedKeys)) + // Label values will have to match keys by index + // so this map will help with fast lookups. + indices := make(map[string]int, len(sortedKeys)) + for i, key := range sortedKeys { + keys = append(keys, &ocmetrics.LabelKey{ + Key: key, + }) + indices[key] = i + } + + return &labelKeys{ + keys: keys, + keyIndices: indices, + } +} + +func collectLabelKeysIntDataPoints(ips pdata.IntDataPointSlice, keySet map[string]struct{}) { + for i := 0; i < ips.Len(); i++ { + addLabelKeys(keySet, ips.At(i).LabelsMap()) + } +} + +func collectLabelKeysDoubleDataPoints(dps pdata.DoubleDataPointSlice, keySet map[string]struct{}) { + for i := 0; i < dps.Len(); i++ { + addLabelKeys(keySet, dps.At(i).LabelsMap()) + } +} + +func collectLabelKeysIntHistogramDataPoints(ihdp pdata.IntHistogramDataPointSlice, keySet map[string]struct{}) { + for i := 0; i < ihdp.Len(); i++ { + addLabelKeys(keySet, ihdp.At(i).LabelsMap()) + } +} + +func collectLabelKeysDoubleHistogramDataPoints(dhdp pdata.DoubleHistogramDataPointSlice, keySet map[string]struct{}) { + for i := 0; i < dhdp.Len(); i++ { + addLabelKeys(keySet, dhdp.At(i).LabelsMap()) + } +} + +func collectLabelKeysDoubleSummaryDataPoints(dhdp pdata.DoubleSummaryDataPointSlice, keySet map[string]struct{}) { + for i := 0; i < dhdp.Len(); i++ { + addLabelKeys(keySet, dhdp.At(i).LabelsMap()) + } +} + +func addLabelKeys(keySet map[string]struct{}, labels pdata.StringMap) { + labels.ForEach(func(k string, v string) { + keySet[k] = struct{}{} + }) +} + +func descriptorToOC(metric pdata.Metric, labelKeys *labelKeys) *ocmetrics.MetricDescriptor { + return &ocmetrics.MetricDescriptor{ + Name: metric.Name(), + Description: metric.Description(), + Unit: metric.Unit(), + Type: descriptorTypeToOC(metric), + LabelKeys: labelKeys.keys, + } +} + +func descriptorTypeToOC(metric pdata.Metric) ocmetrics.MetricDescriptor_Type { + switch metric.DataType() { + case pdata.MetricDataTypeIntGauge: + return ocmetrics.MetricDescriptor_GAUGE_INT64 + case pdata.MetricDataTypeDoubleGauge: + return ocmetrics.MetricDescriptor_GAUGE_DOUBLE + case pdata.MetricDataTypeIntSum: + sd := metric.IntSum() + if sd.IsMonotonic() || sd.AggregationTemporality() == pdata.AggregationTemporalityCumulative { + return ocmetrics.MetricDescriptor_CUMULATIVE_INT64 + } + return ocmetrics.MetricDescriptor_GAUGE_INT64 + case pdata.MetricDataTypeDoubleSum: + sd := metric.DoubleSum() + if sd.IsMonotonic() || sd.AggregationTemporality() == pdata.AggregationTemporalityCumulative { + return ocmetrics.MetricDescriptor_CUMULATIVE_DOUBLE + } + return ocmetrics.MetricDescriptor_GAUGE_DOUBLE + case pdata.MetricDataTypeDoubleHistogram: + hd := metric.DoubleHistogram() + if hd.AggregationTemporality() == pdata.AggregationTemporalityCumulative { + return ocmetrics.MetricDescriptor_CUMULATIVE_DISTRIBUTION + } + return ocmetrics.MetricDescriptor_GAUGE_DISTRIBUTION + case pdata.MetricDataTypeIntHistogram: + hd := metric.IntHistogram() + if hd.AggregationTemporality() == pdata.AggregationTemporalityCumulative { + return ocmetrics.MetricDescriptor_CUMULATIVE_DISTRIBUTION + } + return ocmetrics.MetricDescriptor_GAUGE_DISTRIBUTION + case pdata.MetricDataTypeDoubleSummary: + return ocmetrics.MetricDescriptor_SUMMARY + } + return ocmetrics.MetricDescriptor_UNSPECIFIED +} + +func dataPointsToTimeseries(metric pdata.Metric, labelKeys *labelKeys) []*ocmetrics.TimeSeries { + switch metric.DataType() { + case pdata.MetricDataTypeIntGauge: + return intPointsToOC(metric.IntGauge().DataPoints(), labelKeys) + case pdata.MetricDataTypeDoubleGauge: + return doublePointToOC(metric.DoubleGauge().DataPoints(), labelKeys) + case pdata.MetricDataTypeIntSum: + return intPointsToOC(metric.IntSum().DataPoints(), labelKeys) + case pdata.MetricDataTypeDoubleSum: + return doublePointToOC(metric.DoubleSum().DataPoints(), labelKeys) + case pdata.MetricDataTypeIntHistogram: + return intHistogramPointToOC(metric.IntHistogram().DataPoints(), labelKeys) + case pdata.MetricDataTypeDoubleHistogram: + return doubleHistogramPointToOC(metric.DoubleHistogram().DataPoints(), labelKeys) + case pdata.MetricDataTypeDoubleSummary: + return doubleSummaryPointToOC(metric.DoubleSummary().DataPoints(), labelKeys) + } + + return nil +} + +func intPointsToOC(dps pdata.IntDataPointSlice, labelKeys *labelKeys) []*ocmetrics.TimeSeries { + if dps.Len() == 0 { + return nil + } + timeseries := make([]*ocmetrics.TimeSeries, 0, dps.Len()) + for i := 0; i < dps.Len(); i++ { + ip := dps.At(i) + ts := &ocmetrics.TimeSeries{ + StartTimestamp: pdata.UnixNanoToTimestamp(ip.StartTime()), + LabelValues: labelValuesToOC(ip.LabelsMap(), labelKeys), + Points: []*ocmetrics.Point{ + { + Timestamp: pdata.UnixNanoToTimestamp(ip.Timestamp()), + Value: &ocmetrics.Point_Int64Value{ + Int64Value: ip.Value(), + }, + }, + }, + } + timeseries = append(timeseries, ts) + } + return timeseries +} + +func doublePointToOC(dps pdata.DoubleDataPointSlice, labelKeys *labelKeys) []*ocmetrics.TimeSeries { + if dps.Len() == 0 { + return nil + } + timeseries := make([]*ocmetrics.TimeSeries, 0, dps.Len()) + for i := 0; i < dps.Len(); i++ { + dp := dps.At(i) + ts := &ocmetrics.TimeSeries{ + StartTimestamp: pdata.UnixNanoToTimestamp(dp.StartTime()), + LabelValues: labelValuesToOC(dp.LabelsMap(), labelKeys), + Points: []*ocmetrics.Point{ + { + Timestamp: pdata.UnixNanoToTimestamp(dp.Timestamp()), + Value: &ocmetrics.Point_DoubleValue{ + DoubleValue: dp.Value(), + }, + }, + }, + } + timeseries = append(timeseries, ts) + } + return timeseries +} + +func doubleHistogramPointToOC(dps pdata.DoubleHistogramDataPointSlice, labelKeys *labelKeys) []*ocmetrics.TimeSeries { + if dps.Len() == 0 { + return nil + } + timeseries := make([]*ocmetrics.TimeSeries, 0, dps.Len()) + for i := 0; i < dps.Len(); i++ { + dp := dps.At(i) + buckets := histogramBucketsToOC(dp.BucketCounts()) + doubleExemplarsToOC(dp.ExplicitBounds(), buckets, dp.Exemplars()) + + ts := &ocmetrics.TimeSeries{ + StartTimestamp: pdata.UnixNanoToTimestamp(dp.StartTime()), + LabelValues: labelValuesToOC(dp.LabelsMap(), labelKeys), + Points: []*ocmetrics.Point{ + { + Timestamp: pdata.UnixNanoToTimestamp(dp.Timestamp()), + Value: &ocmetrics.Point_DistributionValue{ + DistributionValue: &ocmetrics.DistributionValue{ + Count: int64(dp.Count()), + Sum: dp.Sum(), + SumOfSquaredDeviation: 0, + BucketOptions: histogramExplicitBoundsToOC(dp.ExplicitBounds()), + Buckets: buckets, + }, + }, + }, + }, + } + timeseries = append(timeseries, ts) + } + return timeseries +} + +func intHistogramPointToOC(dps pdata.IntHistogramDataPointSlice, labelKeys *labelKeys) []*ocmetrics.TimeSeries { + if dps.Len() == 0 { + return nil + } + timeseries := make([]*ocmetrics.TimeSeries, 0, dps.Len()) + for i := 0; i < dps.Len(); i++ { + dp := dps.At(i) + buckets := histogramBucketsToOC(dp.BucketCounts()) + intExemplarsToOC(dp.ExplicitBounds(), buckets, dp.Exemplars()) + + ts := &ocmetrics.TimeSeries{ + StartTimestamp: pdata.UnixNanoToTimestamp(dp.StartTime()), + LabelValues: labelValuesToOC(dp.LabelsMap(), labelKeys), + Points: []*ocmetrics.Point{ + { + Timestamp: pdata.UnixNanoToTimestamp(dp.Timestamp()), + Value: &ocmetrics.Point_DistributionValue{ + DistributionValue: &ocmetrics.DistributionValue{ + Count: int64(dp.Count()), + Sum: float64(dp.Sum()), + SumOfSquaredDeviation: 0, + BucketOptions: histogramExplicitBoundsToOC(dp.ExplicitBounds()), + Buckets: buckets, + }, + }, + }, + }, + } + timeseries = append(timeseries, ts) + } + return timeseries +} + +func histogramExplicitBoundsToOC(bounds []float64) *ocmetrics.DistributionValue_BucketOptions { + if len(bounds) == 0 { + return nil + } + + return &ocmetrics.DistributionValue_BucketOptions{ + Type: &ocmetrics.DistributionValue_BucketOptions_Explicit_{ + Explicit: &ocmetrics.DistributionValue_BucketOptions_Explicit{ + Bounds: bounds, + }, + }, + } +} + +func histogramBucketsToOC(bcts []uint64) []*ocmetrics.DistributionValue_Bucket { + if len(bcts) == 0 { + return nil + } + + ocBuckets := make([]*ocmetrics.DistributionValue_Bucket, 0, len(bcts)) + for _, bucket := range bcts { + ocBuckets = append(ocBuckets, &ocmetrics.DistributionValue_Bucket{ + Count: int64(bucket), + }) + } + return ocBuckets +} + +func doubleSummaryPointToOC(dps pdata.DoubleSummaryDataPointSlice, labelKeys *labelKeys) []*ocmetrics.TimeSeries { + if dps.Len() == 0 { + return nil + } + timeseries := make([]*ocmetrics.TimeSeries, 0, dps.Len()) + for i := 0; i < dps.Len(); i++ { + dp := dps.At(i) + percentileValues := summaryPercentilesToOC(dp.QuantileValues()) + + ts := &ocmetrics.TimeSeries{ + StartTimestamp: pdata.UnixNanoToTimestamp(dp.StartTime()), + LabelValues: labelValuesToOC(dp.LabelsMap(), labelKeys), + Points: []*ocmetrics.Point{ + { + Timestamp: pdata.UnixNanoToTimestamp(dp.Timestamp()), + Value: &ocmetrics.Point_SummaryValue{ + SummaryValue: &ocmetrics.SummaryValue{ + Sum: &wrappers.DoubleValue{Value: dp.Sum()}, + Count: &wrappers.Int64Value{Value: int64(dp.Count())}, + Snapshot: &ocmetrics.SummaryValue_Snapshot{ + PercentileValues: percentileValues, + }, + }, + }, + }, + }, + } + timeseries = append(timeseries, ts) + } + return timeseries +} + +func summaryPercentilesToOC(qtls pdata.ValueAtQuantileSlice) []*ocmetrics.SummaryValue_Snapshot_ValueAtPercentile { + if qtls.Len() == 0 { + return nil + } + + ocPercentiles := make([]*ocmetrics.SummaryValue_Snapshot_ValueAtPercentile, 0, qtls.Len()) + for i := 0; i < qtls.Len(); i++ { + quantile := qtls.At(i) + ocPercentiles = append(ocPercentiles, &ocmetrics.SummaryValue_Snapshot_ValueAtPercentile{ + Percentile: quantile.Quantile() * 100, + Value: quantile.Value(), + }) + } + return ocPercentiles +} + +func doubleExemplarsToOC(bounds []float64, ocBuckets []*ocmetrics.DistributionValue_Bucket, exemplars pdata.DoubleExemplarSlice) { + if exemplars.Len() == 0 { + return + } + + for i := 0; i < exemplars.Len(); i++ { + exemplar := exemplars.At(i) + val := exemplar.Value() + pos := 0 + for ; pos < len(bounds); pos++ { + if val > bounds[pos] { + continue + } + break + } + ocBuckets[pos].Exemplar = exemplarToOC(exemplar.FilteredLabels(), val, exemplar.Timestamp()) + } +} + +func intExemplarsToOC(bounds []float64, ocBuckets []*ocmetrics.DistributionValue_Bucket, exemplars pdata.IntExemplarSlice) { + if exemplars.Len() == 0 { + return + } + + for i := 0; i < exemplars.Len(); i++ { + exemplar := exemplars.At(i) + val := float64(exemplar.Value()) + pos := 0 + for ; pos < len(bounds); pos++ { + if val > bounds[pos] { + continue + } + break + } + ocBuckets[pos].Exemplar = exemplarToOC(exemplar.FilteredLabels(), val, exemplar.Timestamp()) + } +} + +func exemplarToOC(filteredLabels pdata.StringMap, value float64, timestamp pdata.TimestampUnixNano) *ocmetrics.DistributionValue_Exemplar { + var labels map[string]string + if filteredLabels.Len() != 0 { + labels = make(map[string]string, filteredLabels.Len()) + filteredLabels.ForEach(func(k string, v string) { + labels[k] = v + }) + } + + return &ocmetrics.DistributionValue_Exemplar{ + Value: value, + Timestamp: pdata.UnixNanoToTimestamp(timestamp), + Attachments: labels, + } +} + +func labelValuesToOC(labels pdata.StringMap, labelKeys *labelKeys) []*ocmetrics.LabelValue { + if len(labelKeys.keys) == 0 { + return nil + } + + // Initialize label values with defaults + // (The order matches key indices) + labelValuesOrig := make([]ocmetrics.LabelValue, len(labelKeys.keys)) + labelValues := make([]*ocmetrics.LabelValue, len(labelKeys.keys)) + for i := 0; i < len(labelKeys.keys); i++ { + labelValues[i] = &labelValuesOrig[i] + } + + // Visit all defined labels in the point and override defaults with actual values + labels.ForEach(func(k string, v string) { + // Find the appropriate label value that we need to update + keyIndex := labelKeys.keyIndices[k] + labelValue := labelValues[keyIndex] + + // Update label value + labelValue.Value = v + labelValue.HasValue = true + }) + + return labelValues +} diff --git a/internal/otel_collector/translator/internaldata/metrics_to_oc_test.go b/internal/otel_collector/translator/internaldata/metrics_to_oc_test.go new file mode 100644 index 00000000000..e6488e9666c --- /dev/null +++ b/internal/otel_collector/translator/internaldata/metrics_to_oc_test.go @@ -0,0 +1,184 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "testing" + "time" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocmetrics "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func TestMetricsToOC(t *testing.T) { + sampleMetricData := testdata.GeneratMetricsAllTypesWithSampleDatapoints() + attrs := sampleMetricData.ResourceMetrics().At(0).Resource().Attributes() + attrs.Upsert(conventions.AttributeHostName, pdata.NewAttributeValueString("host1")) + attrs.Upsert(conventions.OCAttributeProcessID, pdata.NewAttributeValueInt(123)) + attrs.Upsert(conventions.OCAttributeProcessStartTime, pdata.NewAttributeValueString("2020-02-11T20:26:00Z")) + attrs.Upsert(conventions.AttributeTelemetrySDKLanguage, pdata.NewAttributeValueString("cpp")) + attrs.Upsert(conventions.AttributeTelemetrySDKVersion, pdata.NewAttributeValueString("v2.0.1")) + attrs.Upsert(conventions.OCAttributeExporterVersion, pdata.NewAttributeValueString("v1.2.0")) + + tests := []struct { + name string + internal pdata.Metrics + oc []consumerdata.MetricsData + }{ + { + name: "empty", + internal: testdata.GenerateMetricsEmpty(), + oc: []consumerdata.MetricsData(nil), + }, + + { + name: "one-empty-resource-metrics", + internal: testdata.GenerateMetricsOneEmptyResourceMetrics(), + oc: []consumerdata.MetricsData{ + {}, + }, + }, + + { + name: "no-libraries", + internal: testdata.GenerateMetricsNoLibraries(), + oc: []consumerdata.MetricsData{ + generateOCTestDataNoMetrics(), + }, + }, + + { + name: "one-empty-instrumentation-library", + internal: testdata.GenerateMetricsOneEmptyInstrumentationLibrary(), + oc: []consumerdata.MetricsData{ + generateOCTestDataNoMetrics(), + }, + }, + + { + name: "one-metric-no-resource", + internal: testdata.GenerateMetricsOneMetricNoResource(), + oc: []consumerdata.MetricsData{ + { + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricInt(), + }, + }, + }, + }, + + { + name: "one-metric", + internal: testdata.GenerateMetricsOneMetric(), + oc: []consumerdata.MetricsData{ + generateOCTestDataMetricsOneMetric(), + }, + }, + + { + name: "one-metric-no-labels", + internal: testdata.GenerateMetricsOneMetricNoLabels(), + oc: []consumerdata.MetricsData{ + generateOCTestDataNoLabels(), + }, + }, + + { + name: "all-types-no-data-points", + internal: testdata.GenerateMetricsAllTypesNoDataPoints(), + oc: []consumerdata.MetricsData{ + generateOCTestDataNoPoints(), + }, + }, + + { + name: "sample-metric", + internal: sampleMetricData, + oc: []consumerdata.MetricsData{ + generateOCTestData(), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := MetricsToOC(test.internal) + assert.EqualValues(t, test.oc, got) + }) + } +} + +func TestMetricsToOC_InvalidDataType(t *testing.T) { + internal := testdata.GenerateMetricsMetricTypeInvalid() + want := []consumerdata.MetricsData{ + { + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{ + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestCounterIntMetricName, + Unit: "1", + Type: ocmetrics.MetricDescriptor_UNSPECIFIED, + LabelKeys: nil, + }, + }, + }, + }, + } + got := MetricsToOC(internal) + assert.EqualValues(t, want, got) +} + +func generateOCTestData() consumerdata.MetricsData { + ts := timestamppb.New(time.Date(2020, 2, 11, 20, 26, 0, 0, time.UTC)) + + return consumerdata.MetricsData{ + Node: &occommon.Node{ + Identifier: &occommon.ProcessIdentifier{ + HostName: "host1", + Pid: 123, + StartTimestamp: ts, + }, + LibraryInfo: &occommon.LibraryInfo{ + Language: occommon.LibraryInfo_CPP, + ExporterVersion: "v1.2.0", + CoreLibraryVersion: "v2.0.1", + }, + }, + Resource: &ocresource.Resource{ + Labels: map[string]string{ + "resource-attr": "resource-attr-val-1", + }, + }, + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricInt(), + generateOCTestMetricDouble(), + generateOCTestMetricDoubleHistogram(), + generateOCTestMetricIntHistogram(), + generateOCTestMetricDoubleSummary(), + }, + } +} diff --git a/internal/otel_collector/translator/internaldata/oc_testdata_test.go b/internal/otel_collector/translator/internaldata/oc_testdata_test.go new file mode 100644 index 00000000000..1fffff2471f --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_testdata_test.go @@ -0,0 +1,542 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "time" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocmetrics "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func generateOCTestDataNoMetrics() consumerdata.MetricsData { + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + } +} + +func generateOCTestDataNoPoints() consumerdata.MetricsData { + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{ + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestGaugeDoubleMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_GAUGE_DOUBLE, + }, + }, + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestGaugeIntMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_GAUGE_INT64, + }, + }, + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestCounterDoubleMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_DOUBLE, + }, + }, + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestCounterIntMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_INT64, + }, + }, + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestDoubleHistogramMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + }, + }, + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestIntHistogramMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + }, + }, + { + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestDoubleSummaryMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_SUMMARY, + }, + }, + }, + } +} + +func generateOCTestDataNoLabels() consumerdata.MetricsData { + m := generateOCTestMetricInt() + m.MetricDescriptor.LabelKeys = nil + m.Timeseries[0].LabelValues = nil + m.Timeseries[1].LabelValues = nil + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{m}, + } +} + +func generateOCTestDataMetricsOneMetric() consumerdata.MetricsData { + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{generateOCTestMetricInt()}, + } +} + +func generateOCTestDataMetricsOneMetricOneNil() consumerdata.MetricsData { + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{generateOCTestMetricInt(), nil}, + } +} + +func generateOCTestDataMetricsOneMetricOneNilTimeseries() consumerdata.MetricsData { + m := generateOCTestMetricInt() + m.Timeseries = append(m.Timeseries, nil) + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{m}, + } +} + +func generateOCTestDataMetricsOneMetricOneNilPoint() consumerdata.MetricsData { + m := generateOCTestMetricInt() + m.Timeseries[0].Points = append(m.Timeseries[0].Points, nil) + return consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{ + Labels: map[string]string{"resource-attr": "resource-attr-val-1"}, + }, + Metrics: []*ocmetrics.Metric{m}, + } +} + +func generateOCTestMetricInt() *ocmetrics.Metric { + return &ocmetrics.Metric{ + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestCounterIntMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_INT64, + LabelKeys: []*ocmetrics.LabelKey{ + {Key: testdata.TestLabelKey1}, + {Key: testdata.TestLabelKey2}, + }, + }, + Timeseries: []*ocmetrics.TimeSeries{ + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + Value: testdata.TestLabelValue1, + HasValue: true, + }, + { + // key2 + HasValue: false, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_Int64Value{ + Int64Value: 123, + }, + }, + }, + }, + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + HasValue: false, + }, + { + // key2 + Value: testdata.TestLabelValue2, + HasValue: true, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_Int64Value{ + Int64Value: 456, + }, + }, + }, + }, + }, + } +} + +func generateOCTestMetricDouble() *ocmetrics.Metric { + return &ocmetrics.Metric{ + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: "counter-double", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_DOUBLE, + LabelKeys: []*ocmetrics.LabelKey{ + {Key: testdata.TestLabelKey1}, + {Key: testdata.TestLabelKey2}, + {Key: testdata.TestLabelKey3}, + }, + }, + Timeseries: []*ocmetrics.TimeSeries{ + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + Value: testdata.TestLabelValue1, + HasValue: true, + }, + { + // key2 + Value: testdata.TestLabelValue2, + HasValue: true, + }, + { + // key3 + HasValue: false, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_DoubleValue{ + DoubleValue: 1.23, + }, + }, + }, + }, + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + Value: testdata.TestLabelValue1, + HasValue: true, + }, + { + // key2 + HasValue: false, + }, + { + // key3 + Value: testdata.TestLabelValue3, + HasValue: true, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_DoubleValue{ + DoubleValue: 4.56, + }, + }, + }, + }, + }, + } +} + +func generateOCTestMetricDoubleHistogram() *ocmetrics.Metric { + return &ocmetrics.Metric{ + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestDoubleHistogramMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*ocmetrics.LabelKey{ + {Key: testdata.TestLabelKey1}, + {Key: testdata.TestLabelKey2}, + {Key: testdata.TestLabelKey3}, + }, + }, + Timeseries: []*ocmetrics.TimeSeries{ + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + Value: testdata.TestLabelValue1, + HasValue: true, + }, + { + // key2 + HasValue: false, + }, + { + // key3 + Value: testdata.TestLabelValue3, + HasValue: true, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_DistributionValue{ + DistributionValue: &ocmetrics.DistributionValue{ + Count: 1, + Sum: 15, + }, + }, + }, + }, + }, + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + HasValue: false, + }, + { + // key2 + Value: testdata.TestLabelValue2, + HasValue: true, + }, + { + // key3 + HasValue: false, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_DistributionValue{ + DistributionValue: &ocmetrics.DistributionValue{ + Count: 1, + Sum: 15, + BucketOptions: &ocmetrics.DistributionValue_BucketOptions{ + Type: &ocmetrics.DistributionValue_BucketOptions_Explicit_{ + Explicit: &ocmetrics.DistributionValue_BucketOptions_Explicit{ + Bounds: []float64{1}, + }, + }, + }, + Buckets: []*ocmetrics.DistributionValue_Bucket{ + { + Count: 0, + }, + { + Count: 1, + Exemplar: &ocmetrics.DistributionValue_Exemplar{ + Timestamp: timestamppb.New(testdata.TestMetricExemplarTime), + Value: 15, + Attachments: map[string]string{testdata.TestAttachmentKey: testdata.TestAttachmentValue}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func generateOCTestMetricIntHistogram() *ocmetrics.Metric { + m := generateOCTestMetricDoubleHistogram() + m.MetricDescriptor.Name = testdata.TestIntHistogramMetricName + return m +} + +func generateOCTestMetricDoubleSummary() *ocmetrics.Metric { + return &ocmetrics.Metric{ + MetricDescriptor: &ocmetrics.MetricDescriptor{ + Name: testdata.TestDoubleSummaryMetricName, + Description: "", + Unit: "1", + Type: ocmetrics.MetricDescriptor_SUMMARY, + LabelKeys: []*ocmetrics.LabelKey{ + {Key: testdata.TestLabelKey1}, + {Key: testdata.TestLabelKey2}, + {Key: testdata.TestLabelKey3}, + }, + }, + Timeseries: []*ocmetrics.TimeSeries{ + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + Value: testdata.TestLabelValue1, + HasValue: true, + }, + { + // key2 + HasValue: false, + }, + { + // key3 + Value: testdata.TestLabelValue3, + HasValue: true, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_SummaryValue{ + SummaryValue: &ocmetrics.SummaryValue{ + Count: &wrapperspb.Int64Value{ + Value: 1, + }, + Sum: &wrapperspb.DoubleValue{ + Value: 15, + }, + Snapshot: &ocmetrics.SummaryValue_Snapshot{ + PercentileValues: nil, + }, + }, + }, + }, + }, + }, + { + StartTimestamp: timestamppb.New(testdata.TestMetricStartTime), + LabelValues: []*ocmetrics.LabelValue{ + { + // key1 + HasValue: false, + }, + { + // key2 + Value: testdata.TestLabelValue2, + HasValue: true, + }, + { + // key3 + HasValue: false, + }, + }, + Points: []*ocmetrics.Point{ + { + Timestamp: timestamppb.New(testdata.TestMetricTime), + Value: &ocmetrics.Point_SummaryValue{ + SummaryValue: &ocmetrics.SummaryValue{ + Count: &wrapperspb.Int64Value{ + Value: 1, + }, + Sum: &wrapperspb.DoubleValue{ + Value: 15, + }, + Snapshot: &ocmetrics.SummaryValue_Snapshot{ + PercentileValues: []*ocmetrics.SummaryValue_Snapshot_ValueAtPercentile{ + { + Percentile: 1, + Value: 15, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func generateResourceWithOcNodeAndResource() pdata.Resource { + resource := pdata.NewResource() + resource.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + conventions.OCAttributeProcessStartTime: pdata.NewAttributeValueString("2020-02-11T20:26:00Z"), + conventions.AttributeHostName: pdata.NewAttributeValueString("host1"), + conventions.OCAttributeProcessID: pdata.NewAttributeValueInt(123), + conventions.AttributeTelemetrySDKVersion: pdata.NewAttributeValueString("v2.0.1"), + conventions.OCAttributeExporterVersion: pdata.NewAttributeValueString("v1.2.0"), + conventions.AttributeTelemetrySDKLanguage: pdata.NewAttributeValueString("cpp"), + conventions.OCAttributeResourceType: pdata.NewAttributeValueString("good-resource"), + "node-str-attr": pdata.NewAttributeValueString("node-str-attr-val"), + "resource-str-attr": pdata.NewAttributeValueString("resource-str-attr-val"), + "resource-int-attr": pdata.NewAttributeValueInt(123), + }) + return resource +} + +func generateOcNode() *occommon.Node { + ts := timestamppb.New(time.Date(2020, 2, 11, 20, 26, 0, 0, time.UTC)) + + return &occommon.Node{ + Identifier: &occommon.ProcessIdentifier{ + HostName: "host1", + Pid: 123, + StartTimestamp: ts, + }, + LibraryInfo: &occommon.LibraryInfo{ + Language: occommon.LibraryInfo_CPP, + ExporterVersion: "v1.2.0", + CoreLibraryVersion: "v2.0.1", + }, + Attributes: map[string]string{ + "node-str-attr": "node-str-attr-val", + }, + } +} + +func generateOcResource() *ocresource.Resource { + return &ocresource.Resource{ + Type: "good-resource", + Labels: map[string]string{ + "resource-str-attr": "resource-str-attr-val", + "resource-int-attr": "123", + }, + } +} diff --git a/internal/otel_collector/translator/internaldata/oc_to_metrics.go b/internal/otel_collector/translator/internaldata/oc_to_metrics.go new file mode 100644 index 00000000000..3233f4d4e59 --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_to_metrics.go @@ -0,0 +1,434 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocmetrics "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" +) + +// OCSliceToMetricData converts a slice of OC data format to data.MetricData. +// Deprecated: use pdata.Metrics instead. +func OCSliceToMetrics(ocmds []consumerdata.MetricsData) pdata.Metrics { + metricData := pdata.NewMetrics() + if len(ocmds) == 0 { + return metricData + } + for _, ocmd := range ocmds { + appendOcToMetrics(ocmd, metricData) + } + return metricData +} + +// OCToMetricData converts OC data format to data.MetricData. +// Deprecated: use pdata.Metrics instead. OCToMetrics may be used only by OpenCensus +// receiver and exporter implementations. +func OCToMetrics(md consumerdata.MetricsData) pdata.Metrics { + metricData := pdata.NewMetrics() + appendOcToMetrics(md, metricData) + return metricData +} + +func appendOcToMetrics(md consumerdata.MetricsData, dest pdata.Metrics) { + if md.Node == nil && md.Resource == nil && len(md.Metrics) == 0 { + return + } + + rms := dest.ResourceMetrics() + initialRmsLen := rms.Len() + + if len(md.Metrics) == 0 { + // At least one of the md.Node or md.Resource is not nil. Set the resource and return. + rms.Resize(initialRmsLen + 1) + ocNodeResourceToInternal(md.Node, md.Resource, rms.At(initialRmsLen).Resource()) + return + } + + // We may need to split OC metrics into several ResourceMetrics. OC metrics can have a + // Resource field inside them set to nil which indicates they use the Resource + // specified in "md.Resource", or they can have the Resource field inside them set + // to non-nil which indicates they have overridden Resource field and "md.Resource" + // does not apply to those metrics. + // + // Each OC metric that has its own Resource field set to non-nil must be placed in a + // separate ResourceMetrics instance, containing only that metric. All other OC Metrics + // that have nil Resource field must be placed in one other ResourceMetrics instance, + // which will gets its Resource field from "md.Resource". + // + // We will end up with with one or more ResourceMetrics like this: + // + // ResourceMetrics ResourceMetrics ResourceMetrics + // +-------+-------+---+-------+ +--------------+ +--------------+ + // |Metric1|Metric2|...|MetricM| |Metric | |Metric | ... + // +-------+-------+---+-------+ +--------------+ +--------------+ + + // Count the number of metrics that have nil Resource and need to be combined + // in one slice. + combinedMetricCount := 0 + distinctResourceCount := 0 + for _, ocMetric := range md.Metrics { + if ocMetric == nil { + // Skip nil metrics. + continue + } + if ocMetric.Resource == nil { + combinedMetricCount++ + } else { + distinctResourceCount++ + } + } + // Total number of resources is equal to: + // initial + numMetricsWithResource + (optional) 1 + resourceCount := initialRmsLen + distinctResourceCount + if combinedMetricCount > 0 { + // +1 for all metrics with nil resource + resourceCount++ + } + rms.Resize(resourceCount) + + // Translate "combinedMetrics" first + + if combinedMetricCount > 0 { + rm0 := rms.At(initialRmsLen) + ocNodeResourceToInternal(md.Node, md.Resource, rm0.Resource()) + + // Allocate a slice for metrics that need to be combined into first ResourceMetrics. + ilms := rm0.InstrumentationLibraryMetrics() + ilms.Resize(1) + combinedMetrics := ilms.At(0).Metrics() + combinedMetrics.Resize(combinedMetricCount) + + // Index to next available slot in "combinedMetrics" slice. + combinedMetricIdx := 0 + + for _, ocMetric := range md.Metrics { + if ocMetric == nil { + // Skip nil metrics. + continue + } + + if ocMetric.Resource != nil { + continue // Those are processed separately below. + } + + // Add the metric to the "combinedMetrics". combinedMetrics length is equal + // to combinedMetricCount. The loop above that calculates combinedMetricCount + // has exact same conditions as we have here in this loop. + ocMetricToMetrics(ocMetric, combinedMetrics.At(combinedMetricIdx)) + combinedMetricIdx++ + } + } + + // Translate distinct metrics + + resourceMetricIdx := 0 + if combinedMetricCount > 0 { + // First resourcemetric is used for the default resource, so start with 1. + resourceMetricIdx = 1 + } + for _, ocMetric := range md.Metrics { + if ocMetric == nil { + // Skip nil metrics. + continue + } + + if ocMetric.Resource == nil { + continue // Already processed above. + } + + // This metric has a different Resource and must be placed in a different + // ResourceMetrics instance. Create a separate ResourceMetrics item just for this metric + // and store at resourceMetricIdx. + ocMetricToResourceMetrics(ocMetric, md.Node, rms.At(initialRmsLen+resourceMetricIdx)) + resourceMetricIdx++ + } +} + +func ocMetricToResourceMetrics(ocMetric *ocmetrics.Metric, node *occommon.Node, out pdata.ResourceMetrics) { + ocNodeResourceToInternal(node, ocMetric.Resource, out.Resource()) + ilms := out.InstrumentationLibraryMetrics() + ilms.Resize(1) + metrics := ilms.At(0).Metrics() + metrics.Resize(1) + ocMetricToMetrics(ocMetric, metrics.At(0)) +} + +func ocMetricToMetrics(ocMetric *ocmetrics.Metric, metric pdata.Metric) { + ocDescriptor := ocMetric.GetMetricDescriptor() + if ocDescriptor == nil { + pdata.NewMetric().CopyTo(metric) + return + } + + descriptorType := descriptorTypeToMetrics(ocDescriptor.Type, metric) + if descriptorType == pdata.MetricDataTypeNone { + pdata.NewMetric().CopyTo(metric) + return + } + + metric.SetDescription(ocDescriptor.GetDescription()) + metric.SetName(ocDescriptor.GetName()) + metric.SetUnit(ocDescriptor.GetUnit()) + + setDataPoints(ocMetric, metric) +} + +func descriptorTypeToMetrics(t ocmetrics.MetricDescriptor_Type, metric pdata.Metric) pdata.MetricDataType { + switch t { + case ocmetrics.MetricDescriptor_GAUGE_INT64: + metric.SetDataType(pdata.MetricDataTypeIntGauge) + return pdata.MetricDataTypeIntGauge + case ocmetrics.MetricDescriptor_GAUGE_DOUBLE: + metric.SetDataType(pdata.MetricDataTypeDoubleGauge) + return pdata.MetricDataTypeDoubleGauge + case ocmetrics.MetricDescriptor_CUMULATIVE_INT64: + metric.SetDataType(pdata.MetricDataTypeIntSum) + sum := metric.IntSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return pdata.MetricDataTypeIntSum + case ocmetrics.MetricDescriptor_CUMULATIVE_DOUBLE: + metric.SetDataType(pdata.MetricDataTypeDoubleSum) + sum := metric.DoubleSum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return pdata.MetricDataTypeDoubleSum + case ocmetrics.MetricDescriptor_CUMULATIVE_DISTRIBUTION: + metric.SetDataType(pdata.MetricDataTypeDoubleHistogram) + histo := metric.DoubleHistogram() + histo.SetAggregationTemporality(pdata.AggregationTemporalityCumulative) + return pdata.MetricDataTypeDoubleHistogram + case ocmetrics.MetricDescriptor_SUMMARY: + metric.SetDataType(pdata.MetricDataTypeDoubleSummary) + // no temporality specified for summary metric + return pdata.MetricDataTypeDoubleSummary + } + return pdata.MetricDataTypeNone +} + +// setDataPoints converts OC timeseries to internal datapoints based on metric type +func setDataPoints(ocMetric *ocmetrics.Metric, metric pdata.Metric) { + switch metric.DataType() { + case pdata.MetricDataTypeIntGauge: + fillIntDataPoint(ocMetric, metric.IntGauge().DataPoints()) + case pdata.MetricDataTypeDoubleGauge: + fillDoubleDataPoint(ocMetric, metric.DoubleGauge().DataPoints()) + case pdata.MetricDataTypeIntSum: + fillIntDataPoint(ocMetric, metric.IntSum().DataPoints()) + case pdata.MetricDataTypeDoubleSum: + fillDoubleDataPoint(ocMetric, metric.DoubleSum().DataPoints()) + case pdata.MetricDataTypeDoubleHistogram: + fillDoubleHistogramDataPoint(ocMetric, metric.DoubleHistogram().DataPoints()) + case pdata.MetricDataTypeDoubleSummary: + fillDoubleSummaryDataPoint(ocMetric, metric.DoubleSummary().DataPoints()) + } +} + +func setLabelsMap(ocLabelsKeys []*ocmetrics.LabelKey, ocLabelValues []*ocmetrics.LabelValue, labelsMap pdata.StringMap) { + if len(ocLabelsKeys) == 0 || len(ocLabelValues) == 0 { + return + } + + lablesCount := len(ocLabelsKeys) + + // Handle invalid length of OC label values list + if len(ocLabelValues) < lablesCount { + lablesCount = len(ocLabelValues) + } + + labelsMap.InitEmptyWithCapacity(lablesCount) + for i := 0; i < lablesCount; i++ { + if !ocLabelValues[i].GetHasValue() { + continue + } + labelsMap.Insert(ocLabelsKeys[i].Key, ocLabelValues[i].Value) + } +} + +func fillIntDataPoint(ocMetric *ocmetrics.Metric, dps pdata.IntDataPointSlice) { + ocPointsCount := getPointsCount(ocMetric) + dps.Resize(ocPointsCount) + ocLabelsKeys := ocMetric.GetMetricDescriptor().GetLabelKeys() + pos := 0 + for _, timeseries := range ocMetric.GetTimeseries() { + if timeseries == nil { + continue + } + startTimestamp := pdata.TimestampToUnixNano(timeseries.GetStartTimestamp()) + + for _, point := range timeseries.GetPoints() { + if point == nil { + continue + } + + dp := dps.At(pos) + pos++ + + dp.SetStartTime(startTimestamp) + dp.SetTimestamp(pdata.TimestampToUnixNano(point.GetTimestamp())) + setLabelsMap(ocLabelsKeys, timeseries.LabelValues, dp.LabelsMap()) + dp.SetValue(point.GetInt64Value()) + } + } + dps.Resize(pos) +} + +func fillDoubleDataPoint(ocMetric *ocmetrics.Metric, dps pdata.DoubleDataPointSlice) { + ocPointsCount := getPointsCount(ocMetric) + dps.Resize(ocPointsCount) + ocLabelsKeys := ocMetric.GetMetricDescriptor().GetLabelKeys() + pos := 0 + for _, timeseries := range ocMetric.GetTimeseries() { + if timeseries == nil { + continue + } + startTimestamp := pdata.TimestampToUnixNano(timeseries.GetStartTimestamp()) + + for _, point := range timeseries.GetPoints() { + if point == nil { + continue + } + + dp := dps.At(pos) + pos++ + + dp.SetStartTime(startTimestamp) + dp.SetTimestamp(pdata.TimestampToUnixNano(point.GetTimestamp())) + setLabelsMap(ocLabelsKeys, timeseries.LabelValues, dp.LabelsMap()) + dp.SetValue(point.GetDoubleValue()) + } + } + dps.Resize(pos) +} + +func fillDoubleHistogramDataPoint(ocMetric *ocmetrics.Metric, dps pdata.DoubleHistogramDataPointSlice) { + ocPointsCount := getPointsCount(ocMetric) + dps.Resize(ocPointsCount) + ocLabelsKeys := ocMetric.GetMetricDescriptor().GetLabelKeys() + pos := 0 + for _, timeseries := range ocMetric.GetTimeseries() { + if timeseries == nil { + continue + } + startTimestamp := pdata.TimestampToUnixNano(timeseries.GetStartTimestamp()) + + for _, point := range timeseries.GetPoints() { + if point == nil { + continue + } + + dp := dps.At(pos) + pos++ + + dp.SetStartTime(startTimestamp) + dp.SetTimestamp(pdata.TimestampToUnixNano(point.GetTimestamp())) + setLabelsMap(ocLabelsKeys, timeseries.LabelValues, dp.LabelsMap()) + distributionValue := point.GetDistributionValue() + dp.SetSum(distributionValue.GetSum()) + dp.SetCount(uint64(distributionValue.GetCount())) + ocHistogramBucketsToMetrics(distributionValue.GetBuckets(), dp) + dp.SetExplicitBounds(distributionValue.GetBucketOptions().GetExplicit().GetBounds()) + } + } + dps.Resize(pos) +} + +func fillDoubleSummaryDataPoint(ocMetric *ocmetrics.Metric, dps pdata.DoubleSummaryDataPointSlice) { + ocPointsCount := getPointsCount(ocMetric) + dps.Resize(ocPointsCount) + ocLabelsKeys := ocMetric.GetMetricDescriptor().GetLabelKeys() + pos := 0 + for _, timeseries := range ocMetric.GetTimeseries() { + if timeseries == nil { + continue + } + startTimestamp := pdata.TimestampToUnixNano(timeseries.GetStartTimestamp()) + + for _, point := range timeseries.GetPoints() { + if point == nil { + continue + } + + dp := dps.At(pos) + pos++ + + dp.SetStartTime(startTimestamp) + dp.SetTimestamp(pdata.TimestampToUnixNano(point.GetTimestamp())) + setLabelsMap(ocLabelsKeys, timeseries.LabelValues, dp.LabelsMap()) + summaryValue := point.GetSummaryValue() + dp.SetSum(summaryValue.GetSum().GetValue()) + dp.SetCount(uint64(summaryValue.GetCount().GetValue())) + ocSummaryPercentilesToMetrics(summaryValue.GetSnapshot().GetPercentileValues(), dp) + } + } + dps.Resize(pos) +} + +func ocHistogramBucketsToMetrics(ocBuckets []*ocmetrics.DistributionValue_Bucket, dp pdata.DoubleHistogramDataPoint) { + if len(ocBuckets) == 0 { + return + } + buckets := make([]uint64, len(ocBuckets)) + for i := range buckets { + buckets[i] = uint64(ocBuckets[i].GetCount()) + if ocBuckets[i].GetExemplar() != nil { + exemplar := pdata.NewDoubleExemplar() + exemplarToMetrics(ocBuckets[i].GetExemplar(), exemplar) + dp.Exemplars().Append(exemplar) + } + } + dp.SetBucketCounts(buckets) +} + +func ocSummaryPercentilesToMetrics(ocPercentiles []*ocmetrics.SummaryValue_Snapshot_ValueAtPercentile, dp pdata.DoubleSummaryDataPoint) { + if len(ocPercentiles) == 0 { + return + } + + quantiles := pdata.NewValueAtQuantileSlice() + quantiles.Resize(len(ocPercentiles)) + + for i, percentile := range ocPercentiles { + quantiles.At(i).SetQuantile(percentile.GetPercentile() / 100) + quantiles.At(i).SetValue(percentile.GetValue()) + } + + quantiles.CopyTo(dp.QuantileValues()) +} + +func exemplarToMetrics(ocExemplar *ocmetrics.DistributionValue_Exemplar, exemplar pdata.DoubleExemplar) { + if ocExemplar.GetTimestamp() != nil { + exemplar.SetTimestamp(pdata.TimestampToUnixNano(ocExemplar.GetTimestamp())) + } + exemplar.SetValue(ocExemplar.GetValue()) + attachments := exemplar.FilteredLabels() + ocAttachments := ocExemplar.GetAttachments() + attachments.InitEmptyWithCapacity(len(ocAttachments)) + for k, v := range ocAttachments { + attachments.Upsert(k, v) + } +} + +func getPointsCount(ocMetric *ocmetrics.Metric) int { + timeseriesSlice := ocMetric.GetTimeseries() + var count int + for _, timeseries := range timeseriesSlice { + count += len(timeseries.GetPoints()) + } + return count +} diff --git a/internal/otel_collector/translator/internaldata/oc_to_metrics_test.go b/internal/otel_collector/translator/internaldata/oc_to_metrics_test.go new file mode 100644 index 00000000000..b4d989659ba --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_to_metrics_test.go @@ -0,0 +1,243 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "testing" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocmetrics "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestOCToMetrics(t *testing.T) { + // From OC we never generate Int Histograms, will generate Double Histogram always. + allTypesNoDataPoints := testdata.GenerateMetricsAllTypesNoDataPoints() + dh := allTypesNoDataPoints.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(4) + ih := allTypesNoDataPoints.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(5) + ih.SetDataType(pdata.MetricDataTypeDoubleHistogram) + dh.DoubleHistogram().CopyTo(ih.DoubleHistogram()) + + sampleMetricData := testdata.GeneratMetricsAllTypesWithSampleDatapoints() + dh = sampleMetricData.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(2) + ih = sampleMetricData.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(3) + ih.SetDataType(pdata.MetricDataTypeDoubleHistogram) + dh.DoubleHistogram().CopyTo(ih.DoubleHistogram()) + + tests := []struct { + name string + oc consumerdata.MetricsData + internal pdata.Metrics + }{ + { + name: "empty", + oc: consumerdata.MetricsData{}, + internal: testdata.GenerateMetricsEmpty(), + }, + + { + name: "one-empty-resource-metrics", + oc: consumerdata.MetricsData{ + Node: &occommon.Node{}, + Resource: &ocresource.Resource{}, + }, + internal: testdata.GenerateMetricsOneEmptyResourceMetrics(), + }, + + { + name: "no-libraries", + oc: generateOCTestDataNoMetrics(), + internal: testdata.GenerateMetricsNoLibraries(), + }, + + { + name: "all-types-no-data-points", + oc: generateOCTestDataNoPoints(), + internal: allTypesNoDataPoints, + }, + + { + name: "one-metric-no-labels", + oc: generateOCTestDataNoLabels(), + internal: testdata.GenerateMetricsOneMetricNoLabels(), + }, + + { + name: "one-metric", + oc: generateOCTestDataMetricsOneMetric(), + internal: testdata.GenerateMetricsOneMetric(), + }, + + { + name: "one-metric-one-summary", + oc: consumerdata.MetricsData{ + Resource: generateOCTestResource(), + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricInt(), + generateOCTestMetricDoubleSummary(), + }, + }, + internal: testdata.GenerateMetricsOneCounterOneSummaryMetrics(), + }, + + { + name: "one-metric-one-nil", + oc: generateOCTestDataMetricsOneMetricOneNil(), + internal: testdata.GenerateMetricsOneMetric(), + }, + + { + name: "one-metric-one-nil-timeseries", + oc: generateOCTestDataMetricsOneMetricOneNilTimeseries(), + internal: testdata.GenerateMetricsOneMetric(), + }, + + { + name: "one-metric-one-nil-point", + oc: generateOCTestDataMetricsOneMetricOneNilPoint(), + internal: testdata.GenerateMetricsOneMetric(), + }, + + { + name: "one-metric-one-nil-point", + oc: generateOCTestDataMetricsOneMetricOneNilPoint(), + internal: testdata.GenerateMetricsOneMetric(), + }, + + { + name: "sample-metric", + oc: consumerdata.MetricsData{ + Resource: generateOCTestResource(), + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricInt(), + generateOCTestMetricDouble(), + generateOCTestMetricDoubleHistogram(), + generateOCTestMetricIntHistogram(), + generateOCTestMetricDoubleSummary(), + }, + }, + internal: sampleMetricData, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := OCToMetrics(test.oc) + assert.EqualValues(t, test.internal, got) + + ocslice := []consumerdata.MetricsData{ + test.oc, + test.oc, + } + wantSlice := pdata.NewMetrics() + // Double the ResourceMetrics only if not empty. + if test.internal.ResourceMetrics().Len() != 0 { + test.internal.Clone().ResourceMetrics().MoveAndAppendTo(wantSlice.ResourceMetrics()) + test.internal.Clone().ResourceMetrics().MoveAndAppendTo(wantSlice.ResourceMetrics()) + } + gotSlice := OCSliceToMetrics(ocslice) + assert.EqualValues(t, wantSlice, gotSlice) + }) + } +} + +func TestOCToMetrics_ResourceInMetric(t *testing.T) { + internal := testdata.GenerateMetricsOneMetric() + want := pdata.NewMetrics() + internal.Clone().ResourceMetrics().MoveAndAppendTo(want.ResourceMetrics()) + internal.Clone().ResourceMetrics().MoveAndAppendTo(want.ResourceMetrics()) + want.ResourceMetrics().At(1).Resource().Attributes().UpsertString("resource-attr", "another-value") + oc := generateOCTestDataMetricsOneMetric() + oc2 := generateOCTestDataMetricsOneMetric() + oc.Metrics = append(oc.Metrics, oc2.Metrics...) + oc.Metrics[1].Resource = oc2.Resource + oc.Metrics[1].Resource.Labels["resource-attr"] = "another-value" + got := OCToMetrics(oc) + assert.EqualValues(t, want, got) +} + +func TestOCToMetrics_ResourceInMetricOnly(t *testing.T) { + internal := testdata.GenerateMetricsOneMetric() + want := pdata.NewMetrics() + internal.Clone().ResourceMetrics().MoveAndAppendTo(want.ResourceMetrics()) + oc := generateOCTestDataMetricsOneMetric() + // Move resource to metric level. + // We shouldn't have a "combined" resource after conversion + oc.Metrics[0].Resource = oc.Resource + oc.Resource = nil + got := OCToMetrics(oc) + assert.EqualValues(t, want, got) +} + +func BenchmarkMetricIntOCToMetrics(b *testing.B) { + ocMetric := consumerdata.MetricsData{ + Resource: generateOCTestResource(), + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricInt(), + generateOCTestMetricInt(), + generateOCTestMetricInt(), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + OCToMetrics(ocMetric) + } +} + +func BenchmarkMetricDoubleOCToMetrics(b *testing.B) { + ocMetric := consumerdata.MetricsData{ + Resource: generateOCTestResource(), + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricDouble(), + generateOCTestMetricDouble(), + generateOCTestMetricDouble(), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + OCToMetrics(ocMetric) + } +} + +func BenchmarkMetricHistogramOCToMetrics(b *testing.B) { + ocMetric := consumerdata.MetricsData{ + Resource: generateOCTestResource(), + Metrics: []*ocmetrics.Metric{ + generateOCTestMetricDoubleHistogram(), + generateOCTestMetricDoubleHistogram(), + generateOCTestMetricDoubleHistogram(), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + OCToMetrics(ocMetric) + } +} + +func generateOCTestResource() *ocresource.Resource { + return &ocresource.Resource{ + Labels: map[string]string{ + "resource-attr": "resource-attr-val-1", + }, + } +} diff --git a/internal/otel_collector/translator/internaldata/oc_to_resource.go b/internal/otel_collector/translator/internaldata/oc_to_resource.go new file mode 100644 index 00000000000..310b2ca48eb --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_to_resource.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "time" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +var ocLangCodeToLangMap = getOCLangCodeToLangMap() + +func getOCLangCodeToLangMap() map[occommon.LibraryInfo_Language]string { + mappings := make(map[occommon.LibraryInfo_Language]string) + mappings[1] = conventions.AttributeSDKLangValueCPP + mappings[2] = conventions.AttributeSDKLangValueDotNET + mappings[3] = conventions.AttributeSDKLangValueErlang + mappings[4] = conventions.AttributeSDKLangValueGo + mappings[5] = conventions.AttributeSDKLangValueJava + mappings[6] = conventions.AttributeSDKLangValueNodeJS + mappings[7] = conventions.AttributeSDKLangValuePHP + mappings[8] = conventions.AttributeSDKLangValuePython + mappings[9] = conventions.AttributeSDKLangValueRuby + mappings[10] = conventions.AttributeSDKLangValueWebJS + return mappings +} + +func ocNodeResourceToInternal(ocNode *occommon.Node, ocResource *ocresource.Resource, dest pdata.Resource) { + if ocNode == nil && ocResource == nil { + return + } + + // Number of special fields in OC that will be translated to Attributes + const serviceInfoAttrCount = 1 // Number of Node.ServiceInfo fields. + const nodeIdentifierAttrCount = 3 // Number of Node.Identifier fields. + const libraryInfoAttrCount = 3 // Number of Node.LibraryInfo fields. + const specialResourceAttrCount = 1 // Number of Resource fields. + + // Calculate maximum total number of attributes for capacity reservation. + maxTotalAttrCount := 0 + if ocNode != nil { + maxTotalAttrCount += len(ocNode.Attributes) + if ocNode.ServiceInfo != nil { + maxTotalAttrCount += serviceInfoAttrCount + } + if ocNode.Identifier != nil { + maxTotalAttrCount += nodeIdentifierAttrCount + } + if ocNode.LibraryInfo != nil { + maxTotalAttrCount += libraryInfoAttrCount + } + } + if ocResource != nil { + maxTotalAttrCount += len(ocResource.Labels) + if ocResource.Type != "" { + maxTotalAttrCount += specialResourceAttrCount + } + } + + // There are no attributes to be set. + if maxTotalAttrCount == 0 { + return + } + + attrs := dest.Attributes() + attrs.InitEmptyWithCapacity(maxTotalAttrCount) + + if ocNode != nil { + // Copy all Attributes. + for k, v := range ocNode.Attributes { + attrs.InsertString(k, v) + } + + // Add all special fields. + if ocNode.ServiceInfo != nil { + if ocNode.ServiceInfo.Name != "" { + attrs.UpsertString(conventions.AttributeServiceName, ocNode.ServiceInfo.Name) + } + } + if ocNode.Identifier != nil { + if ocNode.Identifier.StartTimestamp != nil { + attrs.UpsertString(conventions.OCAttributeProcessStartTime, ocNode.Identifier.StartTimestamp.AsTime().Format(time.RFC3339Nano)) + } + if ocNode.Identifier.HostName != "" { + attrs.UpsertString(conventions.AttributeHostName, ocNode.Identifier.HostName) + } + if ocNode.Identifier.Pid != 0 { + attrs.UpsertInt(conventions.OCAttributeProcessID, int64(ocNode.Identifier.Pid)) + } + } + if ocNode.LibraryInfo != nil { + if ocNode.LibraryInfo.CoreLibraryVersion != "" { + attrs.UpsertString(conventions.AttributeTelemetrySDKVersion, ocNode.LibraryInfo.CoreLibraryVersion) + } + if ocNode.LibraryInfo.ExporterVersion != "" { + attrs.UpsertString(conventions.OCAttributeExporterVersion, ocNode.LibraryInfo.ExporterVersion) + } + if ocNode.LibraryInfo.Language != occommon.LibraryInfo_LANGUAGE_UNSPECIFIED { + if str, ok := ocLangCodeToLangMap[ocNode.LibraryInfo.Language]; ok { + attrs.UpsertString(conventions.AttributeTelemetrySDKLanguage, str) + } + } + } + } + + if ocResource != nil { + // Copy resource Labels. + for k, v := range ocResource.Labels { + attrs.InsertString(k, v) + } + // Add special fields. + if ocResource.Type != "" { + attrs.UpsertString(conventions.OCAttributeResourceType, ocResource.Type) + } + } +} diff --git a/internal/otel_collector/translator/internaldata/oc_to_resource_test.go b/internal/otel_collector/translator/internaldata/oc_to_resource_test.go new file mode 100644 index 00000000000..37d85fabe90 --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_to_resource_test.go @@ -0,0 +1,102 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "strings" + "testing" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func TestOcNodeResourceToInternal(t *testing.T) { + resource := pdata.NewResource() + ocNodeResourceToInternal(nil, nil, resource) + assert.Equal(t, 0, resource.Attributes().Len()) + + ocNode := &occommon.Node{} + ocResource := &ocresource.Resource{} + ocNodeResourceToInternal(ocNode, ocResource, resource) + assert.Equal(t, 0, resource.Attributes().Len()) + + ocNode = generateOcNode() + ocResource = generateOcResource() + expectedAttrs := generateResourceWithOcNodeAndResource().Attributes() + // We don't have type information in ocResource, so need to make int attr string + expectedAttrs.Upsert("resource-int-attr", pdata.NewAttributeValueString("123")) + ocNodeResourceToInternal(ocNode, ocResource, resource) + assert.EqualValues(t, expectedAttrs.Sort(), resource.Attributes().Sort()) + + // Make sure hard-coded fields override same-name values in Attributes. + // To do that add Attributes with same-name. + expectedAttrs.ForEach(func(k string, v pdata.AttributeValue) { + // Set all except "attr1" which is not a hard-coded field to some bogus values. + if !strings.Contains(k, "-attr") { + ocNode.Attributes[k] = "this will be overridden 1" + } + }) + ocResource.Labels[conventions.OCAttributeResourceType] = "this will be overridden 2" + + // Convert again. + resource = pdata.NewResource() + ocNodeResourceToInternal(ocNode, ocResource, resource) + // And verify that same-name attributes were ignored. + assert.EqualValues(t, expectedAttrs.Sort(), resource.Attributes().Sort()) +} + +func BenchmarkOcNodeResourceToInternal(b *testing.B) { + ocNode := generateOcNode() + ocResource := generateOcResource() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + resource := pdata.NewResource() + ocNodeResourceToInternal(ocNode, ocResource, resource) + if ocNode.Identifier.Pid != 123 { + b.Fail() + } + } +} + +func BenchmarkOcResourceNodeUnmarshal(b *testing.B) { + oc := &agenttracepb.ExportTraceServiceRequest{ + Node: generateOcNode(), + Spans: nil, + Resource: generateOcResource(), + } + + bytes, err := proto.Marshal(oc) + if err != nil { + b.Fail() + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + unmarshalOc := &agenttracepb.ExportTraceServiceRequest{} + if err := proto.Unmarshal(bytes, unmarshalOc); err != nil { + b.Fail() + } + if unmarshalOc.Node.Identifier.Pid != 123 { + b.Fail() + } + } +} diff --git a/internal/otel_collector/translator/internaldata/oc_to_traces.go b/internal/otel_collector/translator/internaldata/oc_to_traces.go new file mode 100644 index 00000000000..8e83611b646 --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_to_traces.go @@ -0,0 +1,395 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "strings" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + octrace "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "go.opencensus.io/trace" + "google.golang.org/protobuf/types/known/wrapperspb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +// OCToTraceData converts OC data format to Traces. +// Deprecated: use pdata.Traces instead. OCToTraceData may be used only by OpenCensus +// receiver and exporter implementations. +func OCToTraceData(td consumerdata.TraceData) pdata.Traces { + traceData := pdata.NewTraces() + if td.Node == nil && td.Resource == nil && len(td.Spans) == 0 { + return traceData + } + + if len(td.Spans) == 0 { + // At least one of the td.Node or td.Resource is not nil. Set the resource and return. + rss := traceData.ResourceSpans() + rss.Resize(1) + ocNodeResourceToInternal(td.Node, td.Resource, rss.At(0).Resource()) + return traceData + } + + // We may need to split OC spans into several ResourceSpans. OC Spans can have a + // Resource field inside them set to nil which indicates they use the Resource + // specified in "td.Resource", or they can have the Resource field inside them set + // to non-nil which indicates they have overridden Resource field and "td.Resource" + // does not apply to those spans. + // + // Each OC Span that has its own Resource field set to non-nil must be placed in a + // separate ResourceSpans instance, containing only that span. All other OC Spans + // that have nil Resource field must be placed in one other ResourceSpans instance, + // which will gets its Resource field from "td.Resource". + // + // We will end up with with one or more ResourceSpans like this: + // + // ResourceSpans ResourceSpans ResourceSpans + // +-----+-----+---+-----+ +------------+ +------------+ + // |Span1|Span2|...|SpanM| |Span | |Span | ... + // +-----+-----+---+-----+ +------------+ +------------+ + + // Count the number of spans that have nil Resource and need to be combined + // in one slice. + combinedSpanCount := 0 + distinctResourceCount := 0 + for _, ocSpan := range td.Spans { + if ocSpan == nil { + // Skip nil spans. + continue + } + if ocSpan.Resource == nil { + combinedSpanCount++ + } else { + distinctResourceCount++ + } + } + // Total number of resources is equal to: + // 1 (for all spans with nil resource) + numSpansWithResource (distinctResourceCount). + + rss := traceData.ResourceSpans() + rss.Resize(distinctResourceCount + 1) + rs0 := rss.At(0) + ocNodeResourceToInternal(td.Node, td.Resource, rs0.Resource()) + + // Allocate a slice for spans that need to be combined into first ResourceSpans. + ilss := rs0.InstrumentationLibrarySpans() + ilss.Resize(1) + ils0 := ilss.At(0) + combinedSpans := ils0.Spans() + combinedSpans.Resize(combinedSpanCount) + + // Now do the span translation and place them in appropriate ResourceSpans + // instances. + + // Index to next available slot in "combinedSpans" slice. + combinedSpanIdx := 0 + // First ResourceSpans is used for the default resource, so start with 1. + resourceSpanIdx := 1 + for _, ocSpan := range td.Spans { + if ocSpan == nil { + // Skip nil spans. + continue + } + + if ocSpan.Resource == nil { + // Add the span to the "combinedSpans". combinedSpans length is equal + // to combinedSpanCount. The loop above that calculates combinedSpanCount + // has exact same conditions as we have here in this loop. + ocSpanToInternal(ocSpan, combinedSpans.At(combinedSpanIdx)) + combinedSpanIdx++ + } else { + // This span has a different Resource and must be placed in a different + // ResourceSpans instance. Create a separate ResourceSpans item just for this span. + ocSpanToResourceSpans(ocSpan, td.Node, traceData.ResourceSpans().At(resourceSpanIdx)) + resourceSpanIdx++ + } + } + + return traceData +} + +func ocSpanToResourceSpans(ocSpan *octrace.Span, node *occommon.Node, dest pdata.ResourceSpans) { + ocNodeResourceToInternal(node, ocSpan.Resource, dest.Resource()) + ilss := dest.InstrumentationLibrarySpans() + ilss.Resize(1) + ils0 := ilss.At(0) + spans := ils0.Spans() + spans.Resize(1) + ocSpanToInternal(ocSpan, spans.At(0)) +} + +func ocSpanToInternal(src *octrace.Span, dest pdata.Span) { + // Note that ocSpanKindToInternal must be called before initAttributeMapFromOC + // since it may modify src.Attributes (remove the attribute which represents the + // span kind). + dest.SetKind(ocSpanKindToInternal(src.Kind, src.Attributes)) + + dest.SetTraceID(traceIDToInternal(src.TraceId)) + dest.SetSpanID(spanIDToInternal(src.SpanId)) + dest.SetTraceState(ocTraceStateToInternal(src.Tracestate)) + dest.SetParentSpanID(spanIDToInternal(src.ParentSpanId)) + + dest.SetName(src.Name.GetValue()) + dest.SetStartTime(pdata.TimestampToUnixNano(src.StartTime)) + dest.SetEndTime(pdata.TimestampToUnixNano(src.EndTime)) + + ocStatusToInternal(src.Status, src.Attributes, dest.Status()) + + initAttributeMapFromOC(src.Attributes, dest.Attributes()) + dest.SetDroppedAttributesCount(ocAttrsToDroppedAttributes(src.Attributes)) + ocEventsToInternal(src.TimeEvents, dest) + ocLinksToInternal(src.Links, dest) + ocSameProcessAsParentSpanToInternal(src.SameProcessAsParentSpan, dest) +} + +// Transforms the byte slice trace ID into a [16]byte internal pdata.TraceID. +// If larger input then it is truncated to 16 bytes. +func traceIDToInternal(traceID []byte) pdata.TraceID { + tid := [16]byte{} + copy(tid[:], traceID) + return pdata.NewTraceID(tid) +} + +// Transforms the byte slice span ID into a [8]byte internal pdata.SpanID. +// If larger input then it is truncated to 8 bytes. +func spanIDToInternal(spanID []byte) pdata.SpanID { + sid := [8]byte{} + copy(sid[:], spanID) + return pdata.NewSpanID(sid) +} + +func ocStatusToInternal(ocStatus *octrace.Status, ocAttrs *octrace.Span_Attributes, dest pdata.SpanStatus) { + if ocStatus == nil { + return + } + + var code pdata.StatusCode + switch ocStatus.Code { + case trace.StatusCodeOK: + code = pdata.StatusCodeUnset + default: + // all other OC status codes are errors. + code = pdata.StatusCodeError + } + + if ocAttrs != nil { + // tracetranslator.TagStatusCode is set it must override the status code value. + // See the reverse translation in traces_to_oc.go:statusToOC(). + if attr, ok := ocAttrs.AttributeMap[tracetranslator.TagStatusCode]; ok { + code = pdata.StatusCode(attr.GetIntValue()) + delete(ocAttrs.AttributeMap, tracetranslator.TagStatusCode) + } + } + + dest.SetCode(code) + dest.SetMessage(ocStatus.Message) +} + +// Convert tracestate to W3C format. See the https://w3c.github.io/trace-context/ +func ocTraceStateToInternal(ocTracestate *octrace.Span_Tracestate) pdata.TraceState { + if ocTracestate == nil { + return "" + } + var sb strings.Builder + for i, entry := range ocTracestate.Entries { + sb.Grow(1 + len(entry.Key) + 1 + len(entry.Value)) + if i > 0 { + sb.WriteString(",") + } + sb.WriteString(entry.Key) + sb.WriteString("=") + sb.WriteString(entry.Value) + } + return pdata.TraceState(sb.String()) +} + +func ocAttrsToDroppedAttributes(ocAttrs *octrace.Span_Attributes) uint32 { + if ocAttrs == nil { + return 0 + } + return uint32(ocAttrs.DroppedAttributesCount) +} + +// initAttributeMapFromOC initialize AttributeMap from OC attributes +func initAttributeMapFromOC(ocAttrs *octrace.Span_Attributes, dest pdata.AttributeMap) { + if ocAttrs == nil { + return + } + + if len(ocAttrs.AttributeMap) > 0 { + dest.InitEmptyWithCapacity(len(ocAttrs.AttributeMap)) + for key, ocAttr := range ocAttrs.AttributeMap { + switch attribValue := ocAttr.Value.(type) { + case *octrace.AttributeValue_StringValue: + dest.UpsertString(key, attribValue.StringValue.GetValue()) + + case *octrace.AttributeValue_IntValue: + dest.UpsertInt(key, attribValue.IntValue) + + case *octrace.AttributeValue_BoolValue: + dest.UpsertBool(key, attribValue.BoolValue) + + case *octrace.AttributeValue_DoubleValue: + dest.UpsertDouble(key, attribValue.DoubleValue) + + default: + dest.UpsertString(key, "") + } + } + } +} + +func ocSpanKindToInternal(ocKind octrace.Span_SpanKind, ocAttrs *octrace.Span_Attributes) pdata.SpanKind { + switch ocKind { + case octrace.Span_SERVER: + return pdata.SpanKindSERVER + + case octrace.Span_CLIENT: + return pdata.SpanKindCLIENT + + case octrace.Span_SPAN_KIND_UNSPECIFIED: + // Span kind field is unspecified, check if TagSpanKind attribute is set. + // This can happen if span kind had no equivalent in OC, so we could represent it in + // the SpanKind. In that case the span kind may be a special attribute TagSpanKind. + if ocAttrs != nil { + kindAttr := ocAttrs.AttributeMap[tracetranslator.TagSpanKind] + if kindAttr != nil { + strVal, ok := kindAttr.Value.(*octrace.AttributeValue_StringValue) + if ok && strVal != nil { + var otlpKind pdata.SpanKind + switch tracetranslator.OpenTracingSpanKind(strVal.StringValue.GetValue()) { + case tracetranslator.OpenTracingSpanKindConsumer: + otlpKind = pdata.SpanKindCONSUMER + case tracetranslator.OpenTracingSpanKindProducer: + otlpKind = pdata.SpanKindPRODUCER + case tracetranslator.OpenTracingSpanKindInternal: + otlpKind = pdata.SpanKindINTERNAL + default: + return pdata.SpanKindUNSPECIFIED + } + delete(ocAttrs.AttributeMap, tracetranslator.TagSpanKind) + return otlpKind + } + } + } + return pdata.SpanKindUNSPECIFIED + + default: + return pdata.SpanKindUNSPECIFIED + } +} + +func ocEventsToInternal(ocEvents *octrace.Span_TimeEvents, dest pdata.Span) { + if ocEvents == nil { + return + } + + dest.SetDroppedEventsCount(uint32(ocEvents.DroppedMessageEventsCount + ocEvents.DroppedAnnotationsCount)) + + if len(ocEvents.TimeEvent) == 0 { + return + } + + events := dest.Events() + events.Resize(len(ocEvents.TimeEvent)) + + i := 0 + for _, ocEvent := range ocEvents.TimeEvent { + if ocEvent == nil { + // Skip nil source events. + continue + } + + event := events.At(i) + i++ + + event.SetTimestamp(pdata.TimestampToUnixNano(ocEvent.Time)) + + switch teValue := ocEvent.Value.(type) { + case *octrace.Span_TimeEvent_Annotation_: + if teValue.Annotation != nil { + event.SetName(teValue.Annotation.Description.GetValue()) + initAttributeMapFromOC(teValue.Annotation.Attributes, event.Attributes()) + event.SetDroppedAttributesCount(ocAttrsToDroppedAttributes(teValue.Annotation.Attributes)) + } + + case *octrace.Span_TimeEvent_MessageEvent_: + ocMessageEventToInternalAttrs(teValue.MessageEvent, event.Attributes()) + // No dropped attributes for this case. + event.SetDroppedAttributesCount(0) + + default: + event.SetName("An unknown OpenCensus TimeEvent type was detected when translating") + } + } + + // Truncate the slice to only include populated items. + events.Resize(i) +} + +func ocLinksToInternal(ocLinks *octrace.Span_Links, dest pdata.Span) { + if ocLinks == nil { + return + } + + dest.SetDroppedLinksCount(uint32(ocLinks.DroppedLinksCount)) + + if len(ocLinks.Link) == 0 { + return + } + + links := dest.Links() + links.Resize(len(ocLinks.Link)) + + i := 0 + for _, ocLink := range ocLinks.Link { + if ocLink == nil { + continue + } + + link := links.At(i) + i++ + + link.SetTraceID(traceIDToInternal(ocLink.TraceId)) + link.SetSpanID(spanIDToInternal(ocLink.SpanId)) + link.SetTraceState(ocTraceStateToInternal(ocLink.Tracestate)) + initAttributeMapFromOC(ocLink.Attributes, link.Attributes()) + link.SetDroppedAttributesCount(ocAttrsToDroppedAttributes(ocLink.Attributes)) + } + + // Truncate the slice to only include populated items. + links.Resize(i) +} + +func ocMessageEventToInternalAttrs(msgEvent *octrace.Span_TimeEvent_MessageEvent, dest pdata.AttributeMap) { + if msgEvent == nil { + return + } + + dest.UpsertString(conventions.OCTimeEventMessageEventType, msgEvent.Type.String()) + dest.UpsertInt(conventions.OCTimeEventMessageEventID, int64(msgEvent.Id)) + dest.UpsertInt(conventions.OCTimeEventMessageEventUSize, int64(msgEvent.UncompressedSize)) + dest.UpsertInt(conventions.OCTimeEventMessageEventCSize, int64(msgEvent.CompressedSize)) +} + +func ocSameProcessAsParentSpanToInternal(spaps *wrapperspb.BoolValue, dest pdata.Span) { + if spaps == nil { + return + } + dest.Attributes().UpsertBool(conventions.OCAttributeSameProcessAsParentSpan, spaps.Value) +} diff --git a/internal/otel_collector/translator/internaldata/oc_to_traces_test.go b/internal/otel_collector/translator/internaldata/oc_to_traces_test.go new file mode 100644 index 00000000000..29bcd3b5ebb --- /dev/null +++ b/internal/otel_collector/translator/internaldata/oc_to_traces_test.go @@ -0,0 +1,485 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "strconv" + "testing" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + octrace "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func TestOcTraceStateToInternal(t *testing.T) { + assert.EqualValues(t, "", ocTraceStateToInternal(nil)) + + tracestate := &octrace.Span_Tracestate{ + Entries: []*octrace.Span_Tracestate_Entry{ + { + Key: "abc", + Value: "def", + }, + }, + } + assert.EqualValues(t, "abc=def", ocTraceStateToInternal(tracestate)) + + tracestate.Entries = append(tracestate.Entries, + &octrace.Span_Tracestate_Entry{ + Key: "123", + Value: "4567", + }) + assert.EqualValues(t, "abc=def,123=4567", ocTraceStateToInternal(tracestate)) +} + +func TestInitAttributeMapFromOC(t *testing.T) { + attrs := pdata.NewAttributeMap() + initAttributeMapFromOC(nil, attrs) + assert.EqualValues(t, pdata.NewAttributeMap(), attrs) + assert.EqualValues(t, 0, ocAttrsToDroppedAttributes(nil)) + + ocAttrs := &octrace.Span_Attributes{} + attrs = pdata.NewAttributeMap() + initAttributeMapFromOC(ocAttrs, attrs) + assert.EqualValues(t, pdata.NewAttributeMap(), attrs) + assert.EqualValues(t, 0, ocAttrsToDroppedAttributes(ocAttrs)) + + ocAttrs = &octrace.Span_Attributes{ + DroppedAttributesCount: 123, + } + attrs = pdata.NewAttributeMap() + initAttributeMapFromOC(ocAttrs, attrs) + assert.EqualValues(t, pdata.NewAttributeMap(), attrs) + assert.EqualValues(t, 123, ocAttrsToDroppedAttributes(ocAttrs)) + + ocAttrs = &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{}, + DroppedAttributesCount: 234, + } + attrs = pdata.NewAttributeMap() + initAttributeMapFromOC(ocAttrs, attrs) + assert.EqualValues(t, pdata.NewAttributeMap(), attrs) + assert.EqualValues(t, 234, ocAttrsToDroppedAttributes(ocAttrs)) + + ocAttrs = &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "abc": { + Value: &octrace.AttributeValue_StringValue{StringValue: &octrace.TruncatableString{Value: "def"}}, + }, + }, + DroppedAttributesCount: 234, + } + attrs = pdata.NewAttributeMap() + initAttributeMapFromOC(ocAttrs, attrs) + assert.EqualValues(t, + pdata.NewAttributeMap().InitFromMap( + map[string]pdata.AttributeValue{ + "abc": pdata.NewAttributeValueString("def"), + }), + attrs) + assert.EqualValues(t, 234, ocAttrsToDroppedAttributes(ocAttrs)) + + ocAttrs.AttributeMap["intval"] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_IntValue{IntValue: 345}, + } + ocAttrs.AttributeMap["boolval"] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_BoolValue{BoolValue: true}, + } + ocAttrs.AttributeMap["doubleval"] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_DoubleValue{DoubleValue: 4.5}, + } + attrs = pdata.NewAttributeMap() + initAttributeMapFromOC(ocAttrs, attrs) + + expectedAttr := pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + "abc": pdata.NewAttributeValueString("def"), + "intval": pdata.NewAttributeValueInt(345), + "boolval": pdata.NewAttributeValueBool(true), + "doubleval": pdata.NewAttributeValueDouble(4.5), + }) + assert.EqualValues(t, expectedAttr.Sort(), attrs.Sort()) + assert.EqualValues(t, 234, ocAttrsToDroppedAttributes(ocAttrs)) +} + +func TestOcSpanKindToInternal(t *testing.T) { + tests := []struct { + ocAttrs *octrace.Span_Attributes + ocKind octrace.Span_SpanKind + otlpKind otlptrace.Span_SpanKind + }{ + { + ocKind: octrace.Span_CLIENT, + otlpKind: otlptrace.Span_SPAN_KIND_CLIENT, + }, + { + ocKind: octrace.Span_SERVER, + otlpKind: otlptrace.Span_SPAN_KIND_SERVER, + }, + { + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + otlpKind: otlptrace.Span_SPAN_KIND_UNSPECIFIED, + }, + { + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + ocAttrs: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span.kind": {Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "consumer"}}}, + }, + }, + otlpKind: otlptrace.Span_SPAN_KIND_CONSUMER, + }, + { + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + ocAttrs: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span.kind": {Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "producer"}}}, + }, + }, + otlpKind: otlptrace.Span_SPAN_KIND_PRODUCER, + }, + { + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + ocAttrs: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span.kind": {Value: &octrace.AttributeValue_IntValue{ + IntValue: 123}}, + }, + }, + otlpKind: otlptrace.Span_SPAN_KIND_UNSPECIFIED, + }, + { + ocKind: octrace.Span_CLIENT, + ocAttrs: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span.kind": {Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "consumer"}}}, + }, + }, + otlpKind: otlptrace.Span_SPAN_KIND_CLIENT, + }, + { + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + ocAttrs: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span.kind": {Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "internal"}}}, + }, + }, + otlpKind: otlptrace.Span_SPAN_KIND_INTERNAL, + }, + } + + for _, test := range tests { + t.Run(test.otlpKind.String(), func(t *testing.T) { + got := ocSpanKindToInternal(test.ocKind, test.ocAttrs) + assert.EqualValues(t, test.otlpKind, got, "Expected "+test.otlpKind.String()+", got "+got.String()) + }) + } +} + +func TestOcToInternal(t *testing.T) { + ocNode := &occommon.Node{} + ocResource1 := &ocresource.Resource{Labels: map[string]string{"resource-attr": "resource-attr-val-1"}} + ocResource2 := &ocresource.Resource{Labels: map[string]string{"resource-attr": "resource-attr-val-2"}} + + startTime := timestamppb.New(testdata.TestSpanStartTime) + eventTime := timestamppb.New(testdata.TestSpanEventTime) + endTime := timestamppb.New(testdata.TestSpanEndTime) + + ocSpan1 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationA"}, + StartTime: startTime, + EndTime: endTime, + TimeEvents: &octrace.Span_TimeEvents{ + TimeEvent: []*octrace.Span_TimeEvent{ + { + Time: eventTime, + Value: &octrace.Span_TimeEvent_Annotation_{ + Annotation: &octrace.Span_TimeEvent_Annotation{ + Description: &octrace.TruncatableString{Value: "event-with-attr"}, + Attributes: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span-event-attr": { + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-event-attr-val"}, + }, + }, + }, + DroppedAttributesCount: 2, + }, + }, + }, + }, + { + Time: eventTime, + Value: &octrace.Span_TimeEvent_Annotation_{ + Annotation: &octrace.Span_TimeEvent_Annotation{ + Description: &octrace.TruncatableString{Value: "event"}, + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 2, + }, + }, + }, + }, + }, + DroppedAnnotationsCount: 1, + }, + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 1, + }, + Status: &octrace.Status{Message: "status-cancelled", Code: 1}, + } + + // TODO: Create another unit test fully covering ocSpanToInternal + ocSpanZeroedParentID := proto.Clone(ocSpan1).(*octrace.Span) + ocSpanZeroedParentID.ParentSpanId = []byte{0, 0, 0, 0, 0, 0, 0, 0} + + ocSpan2 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationB"}, + StartTime: startTime, + EndTime: endTime, + Links: &octrace.Span_Links{ + Link: []*octrace.Span_Link{ + { + Attributes: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span-link-attr": { + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-link-attr-val"}, + }, + }, + }, + DroppedAttributesCount: 4, + }, + }, + { + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 4, + }, + }, + }, + DroppedLinksCount: 3, + }, + } + + ocSpan3 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationC"}, + StartTime: startTime, + EndTime: endTime, + Resource: ocResource2, + Attributes: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span-attr": { + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-attr-val"}, + }, + }, + }, + DroppedAttributesCount: 5, + }, + } + + tests := []struct { + name string + td pdata.Traces + oc consumerdata.TraceData + }{ + { + name: "empty", + td: testdata.GenerateTraceDataEmpty(), + oc: consumerdata.TraceData{}, + }, + + { + name: "one-empty-resource-spans", + td: testdata.GenerateTraceDataOneEmptyResourceSpans(), + oc: consumerdata.TraceData{Node: ocNode}, + }, + + { + name: "no-libraries", + td: testdata.GenerateTraceDataNoLibraries(), + oc: consumerdata.TraceData{Resource: ocResource1}, + }, + + { + name: "one-span-no-resource", + td: testdata.GenerateTraceDataOneSpanNoResource(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: &ocresource.Resource{}, + Spans: []*octrace.Span{ocSpan1}, + }, + }, + + { + name: "one-span", + td: testdata.GenerateTraceDataOneSpan(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1}, + }, + }, + + { + name: "one-span-zeroed-parent-id", + td: testdata.GenerateTraceDataOneSpan(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpanZeroedParentID}, + }, + }, + + { + name: "one-span-one-nil", + td: testdata.GenerateTraceDataOneSpan(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1, nil}, + }, + }, + + { + name: "two-spans-same-resource", + td: testdata.GenerateTraceDataTwoSpansSameResource(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1, nil, ocSpan2}, + }, + }, + + { + name: "two-spans-same-resource-one-different", + td: testdata.GenerateTraceDataTwoSpansSameResourceOneDifferent(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1, ocSpan2, ocSpan3}, + }, + }, + + { + name: "two-spans-and-separate-in-the-middle", + td: testdata.GenerateTraceDataTwoSpansSameResourceOneDifferent(), + oc: consumerdata.TraceData{ + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1, ocSpan3, ocSpan2}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.EqualValues(t, test.td, OCToTraceData(test.oc)) + }) + } +} + +func TestOcSameProcessAsParentSpanToInternal(t *testing.T) { + span := pdata.NewSpan() + ocSameProcessAsParentSpanToInternal(nil, span) + assert.Equal(t, 0, span.Attributes().Len()) + + ocSameProcessAsParentSpanToInternal(wrapperspb.Bool(false), span) + assert.Equal(t, 1, span.Attributes().Len()) + v, ok := span.Attributes().Get(conventions.OCAttributeSameProcessAsParentSpan) + assert.True(t, ok) + assert.EqualValues(t, pdata.AttributeValueBOOL, v.Type()) + assert.False(t, v.BoolVal()) + + ocSameProcessAsParentSpanToInternal(wrapperspb.Bool(true), span) + assert.Equal(t, 1, span.Attributes().Len()) + v, ok = span.Attributes().Get(conventions.OCAttributeSameProcessAsParentSpan) + assert.True(t, ok) + assert.EqualValues(t, pdata.AttributeValueBOOL, v.Type()) + assert.True(t, v.BoolVal()) +} + +func BenchmarkSpansWithAttributesOCToInternal(b *testing.B) { + ocSpan := generateSpanWithAttributes(15) + + ocTraceData := consumerdata.TraceData{ + Resource: generateOCTestResource(), + Spans: []*octrace.Span{ + ocSpan, + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + OCToTraceData(ocTraceData) + } +} + +func BenchmarkSpansWithAttributesUnmarshal(b *testing.B) { + ocSpan := generateSpanWithAttributes(15) + + bytes, err := proto.Marshal(ocSpan) + if err != nil { + b.Fail() + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + unmarshalOc := &octrace.Span{} + if err := proto.Unmarshal(bytes, unmarshalOc); err != nil { + b.Fail() + } + if len(unmarshalOc.Attributes.AttributeMap) != 15 { + b.Fail() + } + } +} + +func generateSpanWithAttributes(len int) *octrace.Span { + startTime := timestamppb.New(testdata.TestSpanStartTime) + endTime := timestamppb.New(testdata.TestSpanEndTime) + ocSpan2 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationB"}, + StartTime: startTime, + EndTime: endTime, + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 3, + }, + } + + ocSpan2.Attributes.AttributeMap = make(map[string]*octrace.AttributeValue, len) + ocAttr := ocSpan2.Attributes.AttributeMap + for i := 0; i < len; i++ { + ocAttr["span-link-attr_"+strconv.Itoa(i)] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-link-attr-val"}, + }, + } + } + return ocSpan2 +} diff --git a/internal/otel_collector/translator/internaldata/resource_to_oc.go b/internal/otel_collector/translator/internaldata/resource_to_oc.go new file mode 100644 index 00000000000..6c99f2b623c --- /dev/null +++ b/internal/otel_collector/translator/internaldata/resource_to_oc.go @@ -0,0 +1,172 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "strconv" + "time" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "go.opencensus.io/resource/resourcekeys" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +type ocInferredResourceType struct { + // label presence to check against + labelKeyPresent string + // inferred resource type + resourceType string +} + +// mapping of label presence to inferred OC resource type +// NOTE: defined in the priority order (first match wins) +var labelPresenceToResourceType = []ocInferredResourceType{ + { + // See https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/container.md + labelKeyPresent: conventions.AttributeContainerName, + resourceType: resourcekeys.ContainerType, + }, + { + // See https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/k8s.md#pod + labelKeyPresent: conventions.AttributeK8sPod, + // NOTE: OpenCensus is using "k8s" rather than "k8s.pod" for Pod + resourceType: resourcekeys.K8SType, + }, + { + // See https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/host.md + labelKeyPresent: conventions.AttributeHostName, + resourceType: resourcekeys.HostType, + }, + { + // See https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/cloud.md + labelKeyPresent: conventions.AttributeCloudProvider, + resourceType: resourcekeys.CloudType, + }, +} + +var langToOCLangCodeMap = getSDKLangToOCLangCodeMap() + +func getSDKLangToOCLangCodeMap() map[string]int32 { + mappings := make(map[string]int32) + mappings[conventions.AttributeSDKLangValueCPP] = 1 + mappings[conventions.AttributeSDKLangValueDotNET] = 2 + mappings[conventions.AttributeSDKLangValueErlang] = 3 + mappings[conventions.AttributeSDKLangValueGo] = 4 + mappings[conventions.AttributeSDKLangValueJava] = 5 + mappings[conventions.AttributeSDKLangValueNodeJS] = 6 + mappings[conventions.AttributeSDKLangValuePHP] = 7 + mappings[conventions.AttributeSDKLangValuePython] = 8 + mappings[conventions.AttributeSDKLangValueRuby] = 9 + mappings[conventions.AttributeSDKLangValueWebJS] = 10 + return mappings +} + +func internalResourceToOC(resource pdata.Resource) (*occommon.Node, *ocresource.Resource) { + attrs := resource.Attributes() + if attrs.Len() == 0 { + return nil, nil + } + + ocNode := &occommon.Node{} + ocResource := &ocresource.Resource{} + labels := make(map[string]string, attrs.Len()) + attrs.ForEach(func(k string, v pdata.AttributeValue) { + val := tracetranslator.AttributeValueToString(v, false) + + switch k { + case conventions.OCAttributeResourceType: + ocResource.Type = val + case conventions.AttributeServiceName: + getServiceInfo(ocNode).Name = val + case conventions.OCAttributeProcessStartTime: + t, err := time.Parse(time.RFC3339Nano, val) + if err != nil { + return + } + ts := timestamppb.New(t) + getProcessIdentifier(ocNode).StartTimestamp = ts + case conventions.AttributeHostName: + getProcessIdentifier(ocNode).HostName = val + case conventions.OCAttributeProcessID: + pid, err := strconv.Atoi(val) + if err != nil { + pid = defaultProcessID + } + getProcessIdentifier(ocNode).Pid = uint32(pid) + case conventions.AttributeTelemetrySDKVersion: + getLibraryInfo(ocNode).CoreLibraryVersion = val + case conventions.OCAttributeExporterVersion: + getLibraryInfo(ocNode).ExporterVersion = val + case conventions.AttributeTelemetrySDKLanguage: + if code, ok := langToOCLangCodeMap[val]; ok { + getLibraryInfo(ocNode).Language = occommon.LibraryInfo_Language(code) + } + default: + // Not a special attribute, put it into resource labels + labels[k] = val + } + }) + ocResource.Labels = labels + + // If resource type is missing, try to infer it + // based on the presence of resource labels (semantic conventions) + if ocResource.Type == "" { + if resType, ok := inferResourceType(ocResource.Labels); ok { + ocResource.Type = resType + } + } + + return ocNode, ocResource +} + +func getProcessIdentifier(ocNode *occommon.Node) *occommon.ProcessIdentifier { + if ocNode.Identifier == nil { + ocNode.Identifier = &occommon.ProcessIdentifier{} + } + return ocNode.Identifier +} + +func getLibraryInfo(ocNode *occommon.Node) *occommon.LibraryInfo { + if ocNode.LibraryInfo == nil { + ocNode.LibraryInfo = &occommon.LibraryInfo{} + } + return ocNode.LibraryInfo +} + +func getServiceInfo(ocNode *occommon.Node) *occommon.ServiceInfo { + if ocNode.ServiceInfo == nil { + ocNode.ServiceInfo = &occommon.ServiceInfo{} + } + return ocNode.ServiceInfo +} + +func inferResourceType(labels map[string]string) (string, bool) { + if labels == nil { + return "", false + } + + for _, mapping := range labelPresenceToResourceType { + if _, ok := labels[mapping.labelKeyPresent]; ok { + return mapping.resourceType, true + } + } + + return "", false +} diff --git a/internal/otel_collector/translator/internaldata/resource_to_oc_test.go b/internal/otel_collector/translator/internaldata/resource_to_oc_test.go new file mode 100644 index 00000000000..d2e5ccd6b60 --- /dev/null +++ b/internal/otel_collector/translator/internaldata/resource_to_oc_test.go @@ -0,0 +1,288 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "strconv" + "testing" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "go.opencensus.io/resource/resourcekeys" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestResourceToOC(t *testing.T) { + emptyResource := pdata.NewResource() + + ocNode := generateOcNode() + ocResource := generateOcResource() + // We don't differentiate between Node.Attributes and Resource when converting, + // and put everything in Resource. + ocResource.Labels["node-str-attr"] = "node-str-attr-val" + ocNode.Attributes = nil + + tests := []struct { + name string + resource pdata.Resource + ocNode *occommon.Node + ocResource *ocresource.Resource + }{ + { + name: "nil", + resource: pdata.NewResource(), + ocNode: nil, + ocResource: nil, + }, + + { + name: "empty", + resource: emptyResource, + ocNode: nil, + ocResource: nil, + }, + + { + name: "with-attributes", + resource: generateResourceWithOcNodeAndResource(), + ocNode: ocNode, + ocResource: ocResource, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ocNode, ocResource := internalResourceToOC(test.resource) + assert.EqualValues(t, test.ocNode, ocNode) + assert.EqualValues(t, test.ocResource, ocResource) + }) + } +} + +func TestContainerResourceToOC(t *testing.T) { + resource := pdata.NewResource() + resource.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + conventions.AttributeK8sCluster: pdata.NewAttributeValueString("cluster1"), + conventions.AttributeK8sPod: pdata.NewAttributeValueString("pod1"), + conventions.AttributeK8sNamespace: pdata.NewAttributeValueString("namespace1"), + conventions.AttributeContainerName: pdata.NewAttributeValueString("container-name1"), + conventions.AttributeCloudAccount: pdata.NewAttributeValueString("proj1"), + conventions.AttributeCloudZone: pdata.NewAttributeValueString("zone1"), + }) + + want := &ocresource.Resource{ + Type: resourcekeys.ContainerType, // Inferred type + Labels: map[string]string{ + resourcekeys.K8SKeyClusterName: "cluster1", + resourcekeys.K8SKeyPodName: "pod1", + resourcekeys.K8SKeyNamespaceName: "namespace1", + resourcekeys.ContainerKeyName: "container-name1", + resourcekeys.CloudKeyAccountID: "proj1", + resourcekeys.CloudKeyZone: "zone1", + }, + } + + _, ocResource := internalResourceToOC(resource) + if diff := cmp.Diff(want, ocResource, protocmp.Transform()); diff != "" { + t.Errorf("Unexpected difference:\n%v", diff) + } + + // Also test that the explicit resource type is preserved if present + resource.Attributes().InsertString(conventions.OCAttributeResourceType, "other-type") + want.Type = "other-type" + + _, ocResource = internalResourceToOC(resource) + if diff := cmp.Diff(want, ocResource, protocmp.Transform()); diff != "" { + t.Errorf("Unexpected difference:\n%v", diff) + } +} + +func TestAttributeValueToString(t *testing.T) { + assert.EqualValues(t, "", tracetranslator.AttributeValueToString(pdata.NewAttributeValueNull(), false)) + assert.EqualValues(t, "abc", tracetranslator.AttributeValueToString(pdata.NewAttributeValueString("abc"), false)) + assert.EqualValues(t, `"abc"`, tracetranslator.AttributeValueToString(pdata.NewAttributeValueString("abc"), true)) + assert.EqualValues(t, "123", tracetranslator.AttributeValueToString(pdata.NewAttributeValueInt(123), false)) + assert.EqualValues(t, "1.23", tracetranslator.AttributeValueToString(pdata.NewAttributeValueDouble(1.23), false)) + assert.EqualValues(t, "true", tracetranslator.AttributeValueToString(pdata.NewAttributeValueBool(true), false)) + + v := pdata.NewAttributeValueMap() + v.MapVal().InsertString(`a"\`, `b"\`) + v.MapVal().InsertInt("c", 123) + v.MapVal().Insert("d", pdata.NewAttributeValueNull()) + v.MapVal().Insert("e", v) + assert.EqualValues(t, `{"a\"\\":"b\"\\","c":123,"d":null,"e":{"a\"\\":"b\"\\","c":123,"d":null}}`, tracetranslator.AttributeValueToString(v, false)) + + v = pdata.NewAttributeValueArray() + av := pdata.NewAttributeValueString(`b"\`) + v.ArrayVal().Append(av) + av = pdata.NewAttributeValueInt(123) + v.ArrayVal().Append(av) + av = pdata.NewAttributeValueNull() + v.ArrayVal().Append(av) + av = pdata.NewAttributeValueArray() + v.ArrayVal().Append(av) + assert.EqualValues(t, `["b\"\\",123,null,"\u003cInvalid array value\u003e"]`, tracetranslator.AttributeValueToString(v, false)) +} + +func TestInferResourceType(t *testing.T) { + tests := []struct { + name string + labels map[string]string + wantResourceType string + wantOk bool + }{ + { + name: "empty labels", + labels: nil, + wantOk: false, + }, + { + name: "container", + labels: map[string]string{ + conventions.AttributeK8sCluster: "cluster1", + conventions.AttributeK8sPod: "pod1", + conventions.AttributeK8sNamespace: "namespace1", + conventions.AttributeContainerName: "container-name1", + conventions.AttributeCloudAccount: "proj1", + conventions.AttributeCloudZone: "zone1", + }, + wantResourceType: resourcekeys.ContainerType, + wantOk: true, + }, + { + name: "pod", + labels: map[string]string{ + conventions.AttributeK8sCluster: "cluster1", + conventions.AttributeK8sPod: "pod1", + conventions.AttributeK8sNamespace: "namespace1", + conventions.AttributeCloudZone: "zone1", + }, + wantResourceType: resourcekeys.K8SType, + wantOk: true, + }, + { + name: "host", + labels: map[string]string{ + conventions.AttributeK8sCluster: "cluster1", + conventions.AttributeCloudZone: "zone1", + conventions.AttributeHostName: "node1", + }, + wantResourceType: resourcekeys.HostType, + wantOk: true, + }, + { + name: "gce", + labels: map[string]string{ + conventions.AttributeCloudProvider: "gcp", + conventions.AttributeHostID: "inst1", + conventions.AttributeCloudZone: "zone1", + }, + wantResourceType: resourcekeys.CloudType, + wantOk: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + resourceType, ok := inferResourceType(tc.labels) + if tc.wantOk { + assert.True(t, ok) + assert.Equal(t, tc.wantResourceType, resourceType) + } else { + assert.False(t, ok) + assert.Equal(t, "", resourceType) + } + }) + } +} + +func TestResourceToOCAndBack(t *testing.T) { + tests := []goldendataset.PICTInputResource{ + goldendataset.ResourceNil, + goldendataset.ResourceEmpty, + goldendataset.ResourceVMOnPrem, + goldendataset.ResourceVMCloud, + goldendataset.ResourceK8sOnPrem, + goldendataset.ResourceK8sCloud, + goldendataset.ResourceFaas, + goldendataset.ResourceExec, + } + for _, test := range tests { + t.Run(string(test), func(t *testing.T) { + rSpans := make([]*otlptrace.ResourceSpans, 1) + rSpans[0] = &otlptrace.ResourceSpans{ + Resource: goldendataset.GenerateResource(test), + InstrumentationLibrarySpans: nil, + } + traces := pdata.TracesFromOtlp(rSpans) + expected := traces.ResourceSpans().At(0).Resource() + ocNode, ocResource := internalResourceToOC(expected) + actual := pdata.NewResource() + ocNodeResourceToInternal(ocNode, ocResource, actual) + // Remove opencensus resource type from actual. This will be added during translation. + actual.Attributes().Delete(conventions.OCAttributeResourceType) + assert.Equal(t, expected.Attributes().Len(), actual.Attributes().Len()) + expected.Attributes().ForEach(func(k string, v pdata.AttributeValue) { + a, ok := actual.Attributes().Get(k) + assert.True(t, ok) + switch v.Type() { + case pdata.AttributeValueINT: + assert.Equal(t, strconv.FormatInt(v.IntVal(), 10), a.StringVal()) + case pdata.AttributeValueMAP, pdata.AttributeValueARRAY: + assert.Equal(t, a, a) + default: + assert.Equal(t, v, a) + } + }) + }) + } +} + +func BenchmarkInternalResourceToOC(b *testing.B) { + resource := generateResourceWithOcNodeAndResource() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + ocNode, _ := internalResourceToOC(resource) + if ocNode.Identifier.Pid != 123 { + b.Fail() + } + } +} + +func BenchmarkOcResourceNodeMarshal(b *testing.B) { + oc := &agenttracepb.ExportTraceServiceRequest{ + Node: generateOcNode(), + Spans: nil, + Resource: generateOcResource(), + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if _, err := proto.Marshal(oc); err != nil { + b.Fail() + } + } +} diff --git a/internal/otel_collector/translator/internaldata/traces_to_oc.go b/internal/otel_collector/translator/internaldata/traces_to_oc.go new file mode 100644 index 00000000000..518792f7403 --- /dev/null +++ b/internal/otel_collector/translator/internaldata/traces_to_oc.go @@ -0,0 +1,414 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "fmt" + "strings" + + octrace "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "go.opencensus.io/trace" + "google.golang.org/protobuf/types/known/wrapperspb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +const sourceFormat = "otlp_trace" + +var ( + defaultProcessID = 0 +) + +// TraceDataToOC may be used only by OpenCensus receiver and exporter implementations. +// TODO: move this function to OpenCensus package. +func TraceDataToOC(td pdata.Traces) []consumerdata.TraceData { + resourceSpans := td.ResourceSpans() + + if resourceSpans.Len() == 0 { + return nil + } + + ocResourceSpansList := make([]consumerdata.TraceData, 0, resourceSpans.Len()) + + for i := 0; i < resourceSpans.Len(); i++ { + ocResourceSpansList = append(ocResourceSpansList, resourceSpansToOC(resourceSpans.At(i))) + } + + return ocResourceSpansList +} + +func resourceSpansToOC(rs pdata.ResourceSpans) consumerdata.TraceData { + ocTraceData := consumerdata.TraceData{ + SourceFormat: sourceFormat, + } + ocTraceData.Node, ocTraceData.Resource = internalResourceToOC(rs.Resource()) + ilss := rs.InstrumentationLibrarySpans() + if ilss.Len() == 0 { + return ocTraceData + } + // Approximate the number of the spans as the number of the spans in the first + // instrumentation library info. + ocSpans := make([]*octrace.Span, 0, ilss.At(0).Spans().Len()) + for i := 0; i < ilss.Len(); i++ { + ils := ilss.At(i) + // TODO: Handle instrumentation library name and version. + spans := ils.Spans() + for j := 0; j < spans.Len(); j++ { + ocSpans = append(ocSpans, spanToOC(spans.At(j))) + } + } + ocTraceData.Spans = ocSpans + return ocTraceData +} + +func spanToOC(span pdata.Span) *octrace.Span { + spaps := attributesMapToOCSameProcessAsParentSpan(span.Attributes()) + attributes := attributesMapToOCSpanAttributes(span.Attributes(), span.DroppedAttributesCount()) + if kindAttr := spanKindToOCAttribute(span.Kind()); kindAttr != nil { + if attributes == nil { + attributes = &octrace.Span_Attributes{ + AttributeMap: make(map[string]*octrace.AttributeValue, 1), + DroppedAttributesCount: 0, + } + } + attributes.AttributeMap[tracetranslator.TagSpanKind] = kindAttr + } + + ocStatus, statusAttr := statusToOC(span.Status()) + if statusAttr != nil { + if attributes == nil { + attributes = &octrace.Span_Attributes{ + AttributeMap: make(map[string]*octrace.AttributeValue, 1), + DroppedAttributesCount: 0, + } + } + attributes.AttributeMap[tracetranslator.TagStatusCode] = statusAttr + } + + return &octrace.Span{ + TraceId: traceIDToOC(span.TraceID()), + SpanId: spanIDToOC(span.SpanID()), + Tracestate: traceStateToOC(span.TraceState()), + ParentSpanId: spanIDToOC(span.ParentSpanID()), + Name: stringToTruncatableString(span.Name()), + Kind: spanKindToOC(span.Kind()), + StartTime: pdata.UnixNanoToTimestamp(span.StartTime()), + EndTime: pdata.UnixNanoToTimestamp(span.EndTime()), + Attributes: attributes, + TimeEvents: eventsToOC(span.Events(), span.DroppedEventsCount()), + Links: linksToOC(span.Links(), span.DroppedLinksCount()), + Status: ocStatus, + ChildSpanCount: nil, // TODO(dmitryax): Handle once OTLP supports it + SameProcessAsParentSpan: spaps, + } +} + +func attributesMapToOCSpanAttributes(attributes pdata.AttributeMap, droppedCount uint32) *octrace.Span_Attributes { + if attributes.Len() == 0 && droppedCount == 0 { + return nil + } + + return &octrace.Span_Attributes{ + AttributeMap: attributesMapToOCAttributeMap(attributes), + DroppedAttributesCount: int32(droppedCount), + } +} + +func attributesMapToOCAttributeMap(attributes pdata.AttributeMap) map[string]*octrace.AttributeValue { + if attributes.Len() == 0 { + return nil + } + + ocAttributes := make(map[string]*octrace.AttributeValue, attributes.Len()) + attributes.ForEach(func(k string, v pdata.AttributeValue) { + ocAttributes[k] = attributeValueToOC(v) + }) + return ocAttributes +} + +func attributeValueToOC(attr pdata.AttributeValue) *octrace.AttributeValue { + a := &octrace.AttributeValue{} + + switch attr.Type() { + case pdata.AttributeValueSTRING: + a.Value = &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(attr.StringVal()), + } + case pdata.AttributeValueBOOL: + a.Value = &octrace.AttributeValue_BoolValue{ + BoolValue: attr.BoolVal(), + } + case pdata.AttributeValueDOUBLE: + a.Value = &octrace.AttributeValue_DoubleValue{ + DoubleValue: attr.DoubleVal(), + } + case pdata.AttributeValueINT: + a.Value = &octrace.AttributeValue_IntValue{ + IntValue: attr.IntVal(), + } + case pdata.AttributeValueMAP: + a.Value = &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(tracetranslator.AttributeValueToString(attr, false)), + } + case pdata.AttributeValueARRAY: + a.Value = &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(tracetranslator.AttributeValueToString(attr, false)), + } + default: + a.Value = &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(fmt.Sprintf("", attr.Type())), + } + } + + return a +} + +func spanKindToOCAttribute(kind pdata.SpanKind) *octrace.AttributeValue { + var ocKind tracetranslator.OpenTracingSpanKind + switch kind { + case pdata.SpanKindCONSUMER: + ocKind = tracetranslator.OpenTracingSpanKindConsumer + case pdata.SpanKindPRODUCER: + ocKind = tracetranslator.OpenTracingSpanKindProducer + case pdata.SpanKindINTERNAL: + ocKind = tracetranslator.OpenTracingSpanKindInternal + case pdata.SpanKindUNSPECIFIED: + case pdata.SpanKindSERVER: // explicitly handled as SpanKind + case pdata.SpanKindCLIENT: // explicitly handled as SpanKind + default: + + } + + if string(ocKind) == "" { + // No matching kind attribute value + return nil + } + + return stringAttributeValue(string(ocKind)) +} + +func stringAttributeValue(val string) *octrace.AttributeValue { + return &octrace.AttributeValue{ + Value: &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(val), + }, + } +} + +func attributesMapToOCSameProcessAsParentSpan(attr pdata.AttributeMap) *wrapperspb.BoolValue { + val, ok := attr.Get(conventions.OCAttributeSameProcessAsParentSpan) + if !ok || val.Type() != pdata.AttributeValueBOOL { + return nil + } + return wrapperspb.Bool(val.BoolVal()) +} + +// OTLP follows the W3C format, e.g. "vendorname1=opaqueValue1,vendorname2=opaqueValue2" +func traceStateToOC(traceState pdata.TraceState) *octrace.Span_Tracestate { + if traceState == "" { + return nil + } + + // key-value pairs in the "key1=value1" format + pairs := strings.Split(string(traceState), ",") + + entries := make([]*octrace.Span_Tracestate_Entry, 0, len(pairs)) + for _, pair := range pairs { + kv := strings.SplitN(pair, "=", 2) + if len(kv) == 0 { + continue + } + + key := kv[0] + val := "" + if len(kv) >= 2 { + val = kv[1] + } + + entries = append(entries, &octrace.Span_Tracestate_Entry{ + Key: key, + Value: val, + }) + } + + return &octrace.Span_Tracestate{ + Entries: entries, + } +} + +func spanKindToOC(kind pdata.SpanKind) octrace.Span_SpanKind { + switch kind { + case pdata.SpanKindSERVER: + return octrace.Span_SERVER + case pdata.SpanKindCLIENT: + return octrace.Span_CLIENT + // NOTE: see `spanKindToOCAttribute` function for custom kinds + case pdata.SpanKindUNSPECIFIED: + case pdata.SpanKindINTERNAL: + case pdata.SpanKindPRODUCER: + case pdata.SpanKindCONSUMER: + default: + } + + return octrace.Span_SPAN_KIND_UNSPECIFIED +} + +func eventsToOC(events pdata.SpanEventSlice, droppedCount uint32) *octrace.Span_TimeEvents { + if events.Len() == 0 { + if droppedCount == 0 { + return nil + } + return &octrace.Span_TimeEvents{ + TimeEvent: nil, + DroppedMessageEventsCount: int32(droppedCount), + } + } + + ocEvents := make([]*octrace.Span_TimeEvent, 0, events.Len()) + for i := 0; i < events.Len(); i++ { + ocEvents = append(ocEvents, eventToOC(events.At(i))) + } + + return &octrace.Span_TimeEvents{ + TimeEvent: ocEvents, + DroppedAnnotationsCount: int32(droppedCount), + } +} + +func eventToOC(event pdata.SpanEvent) *octrace.Span_TimeEvent { + attrs := event.Attributes() + + // Consider TimeEvent to be of MessageEvent type if all and only relevant attributes are set + ocMessageEventAttrs := []string{ + conventions.OCTimeEventMessageEventType, + conventions.OCTimeEventMessageEventID, + conventions.OCTimeEventMessageEventUSize, + conventions.OCTimeEventMessageEventCSize, + } + // TODO: Find a better way to check for message_event. Maybe use the event.Name. + if attrs.Len() == len(ocMessageEventAttrs) { + ocMessageEventAttrValues := map[string]pdata.AttributeValue{} + var ocMessageEventAttrFound bool + for _, attr := range ocMessageEventAttrs { + akv, found := attrs.Get(attr) + if found { + ocMessageEventAttrFound = true + } + ocMessageEventAttrValues[attr] = akv + } + if ocMessageEventAttrFound { + ocMessageEventType := ocMessageEventAttrValues[conventions.OCTimeEventMessageEventType] + ocMessageEventTypeVal := octrace.Span_TimeEvent_MessageEvent_Type_value[ocMessageEventType.StringVal()] + return &octrace.Span_TimeEvent{ + Time: pdata.UnixNanoToTimestamp(event.Timestamp()), + Value: &octrace.Span_TimeEvent_MessageEvent_{ + MessageEvent: &octrace.Span_TimeEvent_MessageEvent{ + Type: octrace.Span_TimeEvent_MessageEvent_Type(ocMessageEventTypeVal), + Id: uint64(ocMessageEventAttrValues[conventions.OCTimeEventMessageEventID].IntVal()), + UncompressedSize: uint64(ocMessageEventAttrValues[conventions.OCTimeEventMessageEventUSize].IntVal()), + CompressedSize: uint64(ocMessageEventAttrValues[conventions.OCTimeEventMessageEventCSize].IntVal()), + }, + }, + } + } + } + + ocAttributes := attributesMapToOCSpanAttributes(attrs, event.DroppedAttributesCount()) + return &octrace.Span_TimeEvent{ + Time: pdata.UnixNanoToTimestamp(event.Timestamp()), + Value: &octrace.Span_TimeEvent_Annotation_{ + Annotation: &octrace.Span_TimeEvent_Annotation{ + Description: stringToTruncatableString(event.Name()), + Attributes: ocAttributes, + }, + }, + } +} + +func linksToOC(links pdata.SpanLinkSlice, droppedCount uint32) *octrace.Span_Links { + if links.Len() == 0 { + if droppedCount == 0 { + return nil + } + return &octrace.Span_Links{ + Link: nil, + DroppedLinksCount: int32(droppedCount), + } + } + + ocLinks := make([]*octrace.Span_Link, 0, links.Len()) + for i := 0; i < links.Len(); i++ { + link := links.At(i) + ocLink := &octrace.Span_Link{ + TraceId: traceIDToOC(link.TraceID()), + SpanId: spanIDToOC(link.SpanID()), + Tracestate: traceStateToOC(link.TraceState()), + Attributes: attributesMapToOCSpanAttributes(link.Attributes(), link.DroppedAttributesCount()), + } + ocLinks = append(ocLinks, ocLink) + } + + return &octrace.Span_Links{ + Link: ocLinks, + DroppedLinksCount: int32(droppedCount), + } +} + +func traceIDToOC(tid pdata.TraceID) []byte { + if !tid.IsValid() { + return nil + } + tidBytes := tid.Bytes() + return tidBytes[:] +} + +func spanIDToOC(sid pdata.SpanID) []byte { + if !sid.IsValid() { + return nil + } + sidBytes := sid.Bytes() + return sidBytes[:] +} + +func statusToOC(status pdata.SpanStatus) (*octrace.Status, *octrace.AttributeValue) { + var attr *octrace.AttributeValue + var oc int32 + switch status.Code() { + case pdata.StatusCodeUnset: + // Unset in OTLP corresponds to OK in OpenCensus. + oc = trace.StatusCodeOK + case pdata.StatusCodeOk: + // OK in OpenCensus is the closest to OK in OTLP. + oc = trace.StatusCodeOK + // We will also add an attribute to indicate that it is OTLP OK, different from OTLP Unset. + attr = &octrace.AttributeValue{Value: &octrace.AttributeValue_IntValue{IntValue: int64(status.Code())}} + case pdata.StatusCodeError: + oc = trace.StatusCodeUnknown + } + + return &octrace.Status{Code: oc, Message: status.Message()}, attr +} + +func stringToTruncatableString(str string) *octrace.TruncatableString { + if str == "" { + return nil + } + return &octrace.TruncatableString{ + Value: str, + } +} diff --git a/internal/otel_collector/translator/internaldata/traces_to_oc_test.go b/internal/otel_collector/translator/internaldata/traces_to_oc_test.go new file mode 100644 index 00000000000..1fd58cf40b6 --- /dev/null +++ b/internal/otel_collector/translator/internaldata/traces_to_oc_test.go @@ -0,0 +1,446 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internaldata + +import ( + "io" + "math/rand" + "testing" + + occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + ocresource "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + octrace "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestInternalTraceStateToOC(t *testing.T) { + assert.Equal(t, (*octrace.Span_Tracestate)(nil), traceStateToOC("")) + + ocTracestate := &octrace.Span_Tracestate{ + Entries: []*octrace.Span_Tracestate_Entry{ + { + Key: "abc", + Value: "def", + }, + }, + } + assert.EqualValues(t, ocTracestate, traceStateToOC("abc=def")) + + ocTracestate.Entries = append(ocTracestate.Entries, + &octrace.Span_Tracestate_Entry{ + Key: "123", + Value: "4567", + }) + assert.EqualValues(t, ocTracestate, traceStateToOC("abc=def,123=4567")) +} + +func TestAttributesMapToOC(t *testing.T) { + assert.EqualValues(t, (*octrace.Span_Attributes)(nil), attributesMapToOCSpanAttributes(pdata.NewAttributeMap(), 0)) + + ocAttrs := &octrace.Span_Attributes{ + DroppedAttributesCount: 123, + } + assert.EqualValues(t, ocAttrs, attributesMapToOCSpanAttributes(pdata.NewAttributeMap(), 123)) + + ocAttrs = &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "abc": { + Value: &octrace.AttributeValue_StringValue{StringValue: &octrace.TruncatableString{Value: "def"}}, + }, + }, + DroppedAttributesCount: 234, + } + assert.EqualValues(t, ocAttrs, + attributesMapToOCSpanAttributes( + pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + "abc": pdata.NewAttributeValueString("def"), + }), + 234)) + + ocAttrs.AttributeMap["intval"] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_IntValue{IntValue: 345}, + } + ocAttrs.AttributeMap["boolval"] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_BoolValue{BoolValue: true}, + } + ocAttrs.AttributeMap["doubleval"] = &octrace.AttributeValue{ + Value: &octrace.AttributeValue_DoubleValue{DoubleValue: 4.5}, + } + assert.EqualValues(t, ocAttrs, + attributesMapToOCSpanAttributes(pdata.NewAttributeMap().InitFromMap( + map[string]pdata.AttributeValue{ + "abc": pdata.NewAttributeValueString("def"), + "intval": pdata.NewAttributeValueInt(345), + "boolval": pdata.NewAttributeValueBool(true), + "doubleval": pdata.NewAttributeValueDouble(4.5), + }), + 234)) +} + +func TestSpanKindToOC(t *testing.T) { + tests := []struct { + kind pdata.SpanKind + ocKind octrace.Span_SpanKind + }{ + { + kind: pdata.SpanKindCLIENT, + ocKind: octrace.Span_CLIENT, + }, + { + kind: pdata.SpanKindSERVER, + ocKind: octrace.Span_SERVER, + }, + { + kind: pdata.SpanKindCONSUMER, + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + }, + { + kind: pdata.SpanKindPRODUCER, + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + }, + { + kind: pdata.SpanKindUNSPECIFIED, + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + }, + { + kind: pdata.SpanKindINTERNAL, + ocKind: octrace.Span_SPAN_KIND_UNSPECIFIED, + }, + } + + for _, test := range tests { + t.Run(test.kind.String(), func(t *testing.T) { + got := spanKindToOC(test.kind) + assert.EqualValues(t, test.ocKind, got, "Expected "+test.ocKind.String()+", got "+got.String()) + }) + } +} + +func TestAttributesMapTOOcSameProcessAsParentSpan(t *testing.T) { + attr := pdata.NewAttributeMap() + assert.Nil(t, attributesMapToOCSameProcessAsParentSpan(attr)) + + attr.UpsertBool(conventions.OCAttributeSameProcessAsParentSpan, true) + assert.True(t, proto.Equal(wrapperspb.Bool(true), attributesMapToOCSameProcessAsParentSpan(attr))) + + attr.UpsertBool(conventions.OCAttributeSameProcessAsParentSpan, false) + assert.True(t, proto.Equal(wrapperspb.Bool(false), attributesMapToOCSameProcessAsParentSpan(attr))) + + attr.UpdateInt(conventions.OCAttributeSameProcessAsParentSpan, 13) + assert.Nil(t, attributesMapToOCSameProcessAsParentSpan(attr)) +} + +func TestSpanKindToOCAttribute(t *testing.T) { + tests := []struct { + kind pdata.SpanKind + ocAttribute *octrace.AttributeValue + }{ + { + kind: pdata.SpanKindCONSUMER, + ocAttribute: &octrace.AttributeValue{ + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{ + Value: string(tracetranslator.OpenTracingSpanKindConsumer), + }, + }, + }, + }, + { + kind: pdata.SpanKindPRODUCER, + ocAttribute: &octrace.AttributeValue{ + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{ + Value: string(tracetranslator.OpenTracingSpanKindProducer), + }, + }, + }, + }, + { + kind: pdata.SpanKindINTERNAL, + ocAttribute: &octrace.AttributeValue{ + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{ + Value: string(tracetranslator.OpenTracingSpanKindInternal), + }, + }, + }, + }, + { + kind: pdata.SpanKindUNSPECIFIED, + ocAttribute: nil, + }, + { + kind: pdata.SpanKindSERVER, + ocAttribute: nil, + }, + { + kind: pdata.SpanKindCLIENT, + ocAttribute: nil, + }, + } + + for _, test := range tests { + t.Run(test.kind.String(), func(t *testing.T) { + got := spanKindToOCAttribute(test.kind) + assert.EqualValues(t, test.ocAttribute, got, "Expected "+test.ocAttribute.String()+", got "+got.String()) + }) + } +} + +func TestInternalToOC(t *testing.T) { + ocNode := &occommon.Node{} + ocResource1 := &ocresource.Resource{Labels: map[string]string{"resource-attr": "resource-attr-val-1"}} + ocResource2 := &ocresource.Resource{Labels: map[string]string{"resource-attr": "resource-attr-val-2"}} + + startTime := timestamppb.New(testdata.TestSpanStartTime) + eventTime := timestamppb.New(testdata.TestSpanEventTime) + endTime := timestamppb.New(testdata.TestSpanEndTime) + + ocSpan1 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationA"}, + StartTime: startTime, + EndTime: endTime, + TimeEvents: &octrace.Span_TimeEvents{ + TimeEvent: []*octrace.Span_TimeEvent{ + { + Time: eventTime, + Value: &octrace.Span_TimeEvent_Annotation_{ + Annotation: &octrace.Span_TimeEvent_Annotation{ + Description: &octrace.TruncatableString{Value: "event-with-attr"}, + Attributes: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span-event-attr": { + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-event-attr-val"}, + }, + }, + }, + DroppedAttributesCount: 2, + }, + }, + }, + }, + { + Time: eventTime, + Value: &octrace.Span_TimeEvent_Annotation_{ + Annotation: &octrace.Span_TimeEvent_Annotation{ + Description: &octrace.TruncatableString{Value: "event"}, + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 2, + }, + }, + }, + }, + }, + DroppedAnnotationsCount: 1, + }, + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 1, + }, + Status: &octrace.Status{Message: "status-cancelled", Code: 2}, + } + + ocSpan2 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationB"}, + StartTime: startTime, + EndTime: endTime, + Links: &octrace.Span_Links{ + Link: []*octrace.Span_Link{ + { + Attributes: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span-link-attr": { + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-link-attr-val"}, + }, + }, + }, + DroppedAttributesCount: 4, + }, + }, + { + Attributes: &octrace.Span_Attributes{ + DroppedAttributesCount: 4, + }, + }, + }, + DroppedLinksCount: 3, + }, + Status: &octrace.Status{}, + } + + ocSpan3 := &octrace.Span{ + Name: &octrace.TruncatableString{Value: "operationC"}, + StartTime: startTime, + EndTime: endTime, + // TODO: Set resource here and put it in the same TraceDataOld. + Attributes: &octrace.Span_Attributes{ + AttributeMap: map[string]*octrace.AttributeValue{ + "span-attr": { + Value: &octrace.AttributeValue_StringValue{ + StringValue: &octrace.TruncatableString{Value: "span-attr-val"}, + }, + }, + }, + DroppedAttributesCount: 5, + }, + Status: &octrace.Status{}, + } + + tests := []struct { + name string + td pdata.Traces + oc []consumerdata.TraceData + }{ + { + name: "empty", + td: testdata.GenerateTraceDataEmpty(), + oc: []consumerdata.TraceData(nil), + }, + + { + name: "one-empty-resource-spans", + td: testdata.GenerateTraceDataOneEmptyResourceSpans(), + oc: []consumerdata.TraceData{ + { + Node: nil, + Resource: nil, + Spans: []*octrace.Span(nil), + SourceFormat: sourceFormat, + }, + }, + }, + + { + name: "no-libraries", + td: testdata.GenerateTraceDataNoLibraries(), + oc: []consumerdata.TraceData{ + { + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span(nil), + SourceFormat: sourceFormat, + }, + }, + }, + + { + name: "one-empty-instrumentation-library", + td: testdata.GenerateTraceDataOneEmptyInstrumentationLibrary(), + oc: []consumerdata.TraceData{ + { + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{}, + SourceFormat: sourceFormat, + }, + }, + }, + + { + name: "one-span-no-resource", + td: testdata.GenerateTraceDataOneSpanNoResource(), + oc: []consumerdata.TraceData{ + { + Node: nil, + Resource: nil, + Spans: []*octrace.Span{ocSpan1}, + SourceFormat: sourceFormat, + }, + }, + }, + + { + name: "one-span", + td: testdata.GenerateTraceDataOneSpan(), + oc: []consumerdata.TraceData{ + { + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1}, + SourceFormat: sourceFormat, + }, + }, + }, + + { + name: "two-spans-same-resource", + td: testdata.GenerateTraceDataTwoSpansSameResource(), + oc: []consumerdata.TraceData{ + { + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1, ocSpan2}, + SourceFormat: sourceFormat, + }, + }, + }, + + { + name: "two-spans-same-resource-one-different", + td: testdata.GenerateTraceDataTwoSpansSameResourceOneDifferent(), + oc: []consumerdata.TraceData{ + { + Node: ocNode, + Resource: ocResource1, + Spans: []*octrace.Span{ocSpan1, ocSpan2}, + SourceFormat: sourceFormat, + }, + { + Node: ocNode, + Resource: ocResource2, + Spans: []*octrace.Span{ocSpan3}, + SourceFormat: sourceFormat, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.EqualValues(t, test.oc, TraceDataToOC(test.td)) + }) + } +} + +func TestInternalTracesToOCTracesAndBack(t *testing.T) { + rscSpans, err := goldendataset.GenerateResourceSpans( + "../../internal/goldendataset/testdata/generated_pict_pairs_traces.txt", + "../../internal/goldendataset/testdata/generated_pict_pairs_spans.txt", + io.Reader(rand.New(rand.NewSource(2004)))) + assert.NoError(t, err) + for _, rs := range rscSpans { + orig := make([]*otlptrace.ResourceSpans, 1) + orig[0] = rs + td := pdata.TracesFromOtlp(orig) + ocTraceData := TraceDataToOC(td) + assert.Equal(t, 1, len(ocTraceData)) + assert.Equal(t, td.SpanCount(), len(ocTraceData[0].Spans)) + tdFromOC := OCToTraceData(ocTraceData[0]) + assert.NotNil(t, tdFromOC) + assert.Equal(t, td.SpanCount(), tdFromOC.SpanCount()) + } +} diff --git a/internal/otel_collector/translator/trace/README.md b/internal/otel_collector/translator/trace/README.md new file mode 100644 index 00000000000..4fa90d504af --- /dev/null +++ b/internal/otel_collector/translator/trace/README.md @@ -0,0 +1,74 @@ +# Overview + +This package implements a number of translators that help translate spans to and from OpenCensus format to a number of other supported formats such as Jaeger Proto, Jaeger Thrift, Zipkin Thrift, Zipkin JSON. This document mentions how certain non-obvious things should be handled. + +## Links: + +* [OpenTracing Semantic Conventions](https://github.com/opentracing/specification/blob/master/semantic_conventions.md) + +## Status Codes and Messages + +### OpenCensus + +OpenCensus protocol has a special field to represent the status of an operation. The status field has two fields, an int32 field called `code` and a string field called `message`. When converting from other formats, status field must be set from the relevant tags/attributes of the source format. When converting from OC to other formats, the status field must be translated to appropriate tags/attributes of the target format. + + +### Jaeger to OC + +Jaeger spans may contain two possible sets of tags that can possibly represent the status of an operation: + +- `status.code` and `status.message` +- `http.status_code` and `http.status_message` + +When converting from Jaeger to OC, + +1. OC status should be set from `status.code` and `status.message` tags if `status.code` tag is found on the Jaeger span. Since OC already has a special status field, these tags (`status.code` and `status.message`) are redundant and should be dropped from resultant OC span. +2. If the `status.code` tag is not present, status should be set from `http.status_code` and `http.status_message` if the `http.status_code` tag is found. HTTP status code should be mapped to the appropriate gRPC status code before using it in OC status. These tags should be preserved and added to the resultant OC span as attributes. +3. If none of the tags are found, OC status should not be set. + + +### Zipkin to OC + +In addition to the two sets of tags mentioned in the previous section, Zipkin spans can possibly contain a third set of tags to represent operation status resulting in the following sets of tags: + +- `census.status_code` and `census.status_description` +- `status.code` and `status.message` +- `http.status_code` and `http.status_message` + +When converting from Zipkin to OC, + +1. OC status should be set from `census.status_code` and `census.status_description` if `census.status_code` tag is found on the Zipkin span. These tags should be dropped from the resultant OC span. +2. If the `census.status_code` tag is not found in step 1, OC status should be set from `status.code` and `status.message` tags if the `status.code` tag is present. The tags should be dropped from the resultant OC span. +3. If no tags are found in step 1 and 2, OC status should be set from `http.status_code` and `http.status_message` if either `http.status_code` tag is found. These tags should be preserved and added to the resultant OC span as attributes. +4. If none of the tags are found, OC status should not be set. + + +Note that codes and messages from different sets of tags should not be mixed to form the status field. For example, OC status should not contain code from `http.status_code` but message from `status.message` and vice-versa. Both fields must be set from the same set of tags even if it means leaving one of the two fields empty. + + +### OC to Jaeger + +When converting from OC to Jaeger, if the OC span has a status field set, then + +* `code` should be added as a `status.code` tag. +* `message` should be added as a `status.message` tag. + +### OC to Zipkin + +When converting from OC to Zipkin, if the OC span has the status field set, then + +* `code` should be added as a `census.status_code` tag. +* `message` should be added as a `census.status_description` tag. + +In addition to this, if the OC status field represents a non-OK status, then a tag with the key `error` should be added and set to the same value as that of the status message falling back to status code when status message is not available. + +### Note: + +If either target tags (`status.*` or `census.status_*`) are already present on the span, then they should be preserved and not overwritten from the status field. This is extremely unlikely to happen within the collector because of how things are implemented but any other implementations should still follow this rule. + + +## Converting HTTP status codes to OC codes + +The following guidelines should be followed for translating HTTP status codes to OC ones. https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto + +This is implemented by the `tracetranslator` package as `HTTPToOCCodeMapper`. diff --git a/internal/otel_collector/translator/trace/big_endian_converter.go b/internal/otel_collector/translator/trace/big_endian_converter.go new file mode 100644 index 00000000000..25daea54e6d --- /dev/null +++ b/internal/otel_collector/translator/trace/big_endian_converter.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + "encoding/binary" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// UInt64ToByteTraceID takes a two uint64 representation of a TraceID and +// converts it to a []byte representation. +func UInt64ToTraceID(high, low uint64) pdata.TraceID { + traceID := [16]byte{} + binary.BigEndian.PutUint64(traceID[:8], high) + binary.BigEndian.PutUint64(traceID[8:], low) + return pdata.NewTraceID(traceID) +} + +// UInt64ToByteTraceID takes a two uint64 representation of a TraceID and +// converts it to a []byte representation. +func UInt64ToByteTraceID(high, low uint64) [16]byte { + traceID := [16]byte{} + binary.BigEndian.PutUint64(traceID[:8], high) + binary.BigEndian.PutUint64(traceID[8:], low) + return traceID +} + +// Int64ToByteTraceID takes a two int64 representation of a TraceID and +// converts it to a []byte representation. +func Int64ToTraceID(high, low int64) pdata.TraceID { + return UInt64ToTraceID(uint64(high), uint64(low)) +} + +// Int64ToByteTraceID takes a two int64 representation of a TraceID and +// converts it to a []byte representation. +func Int64ToByteTraceID(high, low int64) [16]byte { + return UInt64ToByteTraceID(uint64(high), uint64(low)) +} + +// BytesToUInt64TraceID takes a []byte representation of a TraceID and +// converts it to a two uint64 representation. +func BytesToUInt64TraceID(traceID [16]byte) (uint64, uint64) { + return binary.BigEndian.Uint64(traceID[:8]), binary.BigEndian.Uint64(traceID[8:]) +} + +// BytesToInt64TraceID takes a []byte representation of a TraceID and +// converts it to a two int64 representation. +func BytesToInt64TraceID(traceID [16]byte) (int64, int64) { + traceIDHigh, traceIDLow := BytesToUInt64TraceID(traceID) + return int64(traceIDHigh), int64(traceIDLow) +} + +// TraceIDToUInt64Pair takes a pdata.TraceID and converts it to a pair of uint64 representation. +func TraceIDToUInt64Pair(traceID pdata.TraceID) (uint64, uint64) { + return BytesToUInt64TraceID(traceID.Bytes()) +} + +// UInt64ToByteSpanID takes a uint64 representation of a SpanID and +// converts it to a []byte representation. +func UInt64ToByteSpanID(id uint64) [8]byte { + spanID := [8]byte{} + binary.BigEndian.PutUint64(spanID[:], id) + return spanID +} + +// UInt64ToSpanID takes a uint64 representation of a SpanID and +// converts it to a pdata.SpanID representation. +func UInt64ToSpanID(id uint64) pdata.SpanID { + return pdata.NewSpanID(UInt64ToByteSpanID(id)) +} + +// Int64ToByteSpanID takes a int64 representation of a SpanID and +// converts it to a []byte representation. +func Int64ToByteSpanID(id int64) [8]byte { + return UInt64ToByteSpanID(uint64(id)) +} + +// Int64ToByteSpanID takes a int64 representation of a SpanID and +// converts it to a []byte representation. +func Int64ToSpanID(id int64) pdata.SpanID { + return UInt64ToSpanID(uint64(id)) +} + +// BytesToUInt64SpanID takes a []byte representation of a SpanID and +// converts it to a uint64 representation. +func BytesToUInt64SpanID(b [8]byte) uint64 { + return binary.BigEndian.Uint64(b[:]) +} + +// BytesToInt64SpanID takes a []byte representation of a SpanID and +// converts it to a int64 representation. +func BytesToInt64SpanID(b [8]byte) int64 { + return int64(BytesToUInt64SpanID(b)) +} diff --git a/internal/otel_collector/translator/trace/big_endian_converter_test.go b/internal/otel_collector/translator/trace/big_endian_converter_test.go new file mode 100644 index 00000000000..a90e0961a70 --- /dev/null +++ b/internal/otel_collector/translator/trace/big_endian_converter_test.go @@ -0,0 +1,126 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUInt64ToBytesTraceIDConversion(t *testing.T) { + assert.Equal(t, + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + UInt64ToByteTraceID(0, 0), + "Failed 0 conversion:") + assert.Equal(t, + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, + UInt64ToByteTraceID(256*256+256+1, 256+1), + "Failed simple conversion:") + assert.Equal(t, + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + UInt64ToByteTraceID(0, 5), + "Failed to convert 0 high:") + assert.Equal(t, + UInt64ToByteTraceID(5, 0), + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + UInt64ToByteTraceID(5, 0), + "Failed to convert 0 low:") + assert.Equal(t, + [16]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + UInt64ToByteTraceID(math.MaxUint64, 5), + "Failed to convert MaxUint64:") +} + +func TestInt64ToBytesTraceIDConversion(t *testing.T) { + assert.Equal(t, + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Int64ToByteTraceID(0, 0), + "Failed 0 conversion:") + assert.Equal(t, + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + Int64ToByteTraceID(0, -1), + "Failed to convert negative low:") + assert.Equal(t, + [16]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + Int64ToByteTraceID(-2, 5), + "Failed to convert negative high:") + assert.Equal(t, + [16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Int64ToByteTraceID(5, math.MinInt64), + "Failed to convert MinInt64:") +} + +func TestUInt64ToBytesSpanIDConversion(t *testing.T) { + assert.Equal(t, + [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + UInt64ToByteSpanID(0), + "Failed 0 conversion:") + assert.Equal(t, + [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01}, + UInt64ToByteSpanID(256*256+256+1), + "Failed simple conversion:") + assert.Equal(t, + [8]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + UInt64ToByteSpanID(math.MaxUint64), + "Failed to convert MaxUint64:") +} + +func TestInt64ToBytesSpanIDConversion(t *testing.T) { + assert.Equal(t, + [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Int64ToByteSpanID(0), + "Failed 0 conversion:") + assert.Equal(t, + [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + Int64ToByteSpanID(5), + "Failed to convert positive id:") + assert.Equal(t, + [8]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + Int64ToByteSpanID(-1), + "Failed to convert negative id:") + assert.Equal(t, + [8]byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + Int64ToByteSpanID(math.MinInt64), + "Failed to convert MinInt64:") +} + +func TestTraceIDInt64RoundTrip(t *testing.T) { + wh := int64(0x70605040302010FF) + wl := int64(0x0001020304050607) + gh, gl := BytesToInt64TraceID(Int64ToByteTraceID(wh, wl)) + if gh != wh || gl != wl { + t.Errorf("Round trip of TraceID failed:\n\tGot: (0x%0x, 0x%0x)\n\tWant: (0x%0x, 0x%0x)", gl, gh, wl, wh) + } +} + +func TestTraceIDUInt64RoundTrip(t *testing.T) { + wh := uint64(0x70605040302010FF) + wl := uint64(0x0001020304050607) + gh, gl := BytesToUInt64TraceID(UInt64ToByteTraceID(wh, wl)) + assert.Equal(t, wl, gl) + assert.Equal(t, wh, gh) +} + +func TestSpanIdInt64RoundTrip(t *testing.T) { + w := int64(0x0001020304050607) + assert.Equal(t, w, BytesToInt64SpanID(Int64ToByteSpanID(w))) +} + +func TestSpanIdUInt64RoundTrip(t *testing.T) { + w := uint64(0x0001020304050607) + assert.Equal(t, w, BytesToUInt64SpanID(UInt64ToByteSpanID(w))) +} diff --git a/internal/otel_collector/translator/trace/grpc_http_mapper.go b/internal/otel_collector/translator/trace/grpc_http_mapper.go new file mode 100644 index 00000000000..f997f3a3a5e --- /dev/null +++ b/internal/otel_collector/translator/trace/grpc_http_mapper.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + "net/http" +) + +// https://github.com/googleapis/googleapis/blob/bee79fbe03254a35db125dc6d2f1e9b752b390fe/google/rpc/code.proto#L33-L186 +const ( + OCOK = 0 + OCCancelled = 1 + OCUnknown = 2 + OCInvalidArgument = 3 + OCDeadlineExceeded = 4 + OCNotFound = 5 + OCAlreadyExists = 6 + OCPermissionDenied = 7 + OCResourceExhausted = 8 + OCFailedPrecondition = 9 + OCAborted = 10 + OCOutOfRange = 11 + OCUnimplemented = 12 + OCInternal = 13 + OCUnavailable = 14 + OCDataLoss = 15 + OCUnauthenticated = 16 +) + +var httpToOCCodeMap = map[int32]int32{ + 401: OCUnauthenticated, + 403: OCPermissionDenied, + 404: OCNotFound, + 429: OCResourceExhausted, + 499: OCCancelled, + 501: OCUnimplemented, + 503: OCUnavailable, + 504: OCDeadlineExceeded, +} + +// OCStatusCodeFromHTTP takes an HTTP status code and return the appropriate OpenTelemetry status code +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md +func OCStatusCodeFromHTTP(code int32) int32 { + if code >= 100 && code < 400 { + return OCOK + } + if c, ok := httpToOCCodeMap[code]; ok { + return c + } + if code >= 400 && code < 500 { + return OCInvalidArgument + } + if code >= 500 && code < 600 { + return OCInternal + } + return OCUnknown +} + +var ocToHTTPCodeMap = map[int32]int32{ + OCOK: http.StatusOK, + OCCancelled: 499, + OCUnknown: http.StatusInternalServerError, + OCInvalidArgument: http.StatusBadRequest, + OCDeadlineExceeded: http.StatusGatewayTimeout, + OCNotFound: http.StatusNotFound, + OCAlreadyExists: http.StatusConflict, + OCPermissionDenied: http.StatusForbidden, + OCResourceExhausted: http.StatusTooManyRequests, + OCFailedPrecondition: http.StatusPreconditionFailed, + OCAborted: http.StatusConflict, + OCOutOfRange: http.StatusRequestedRangeNotSatisfiable, + OCUnimplemented: http.StatusNotImplemented, + OCInternal: http.StatusInternalServerError, + OCUnavailable: http.StatusServiceUnavailable, + OCDataLoss: http.StatusUnprocessableEntity, + OCUnauthenticated: http.StatusUnauthorized, +} + +// HTTPStatusCodeFromOCStatus takes an OpenTelemetry status code and return the appropriate HTTP status code +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md +func HTTPStatusCodeFromOCStatus(code int32) int32 { + if c, ok := ocToHTTPCodeMap[code]; ok { + return c + } + return http.StatusInternalServerError +} diff --git a/internal/otel_collector/translator/trace/grpc_http_mapper_test.go b/internal/otel_collector/translator/trace/grpc_http_mapper_test.go new file mode 100644 index 00000000000..c3db0539a12 --- /dev/null +++ b/internal/otel_collector/translator/trace/grpc_http_mapper_test.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHTTPStatusFromOTStatus(t *testing.T) { + for otelStatus := int32(OCOK); otelStatus <= OCUnauthenticated; otelStatus++ { + httpStatus := HTTPStatusCodeFromOCStatus(otelStatus) + assert.True(t, httpStatus != 0) + } +} + +func TestOTStatusFromHTTPStatus(t *testing.T) { + for httpStatus := int32(100); httpStatus <= 604; httpStatus++ { + otelStatus := OCStatusCodeFromHTTP(httpStatus) + assert.True(t, otelStatus >= OCOK && otelStatus <= OCUnauthenticated) + } +} diff --git a/internal/otel_collector/translator/trace/jaeger/constants.go b/internal/otel_collector/translator/trace/jaeger/constants.go new file mode 100644 index 00000000000..48925f21e41 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/constants.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "errors" +) + +var ( + errZeroTraceID = errors.New("OC span has an all zeros trace ID") + errZeroSpanID = errors.New("OC span has an all zeros span ID") +) diff --git a/internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces.go b/internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces.go new file mode 100644 index 00000000000..19e8e06b0d1 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces.go @@ -0,0 +1,377 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "encoding/base64" + "fmt" + "math" + "reflect" + "strconv" + + "github.com/jaegertracing/jaeger/model" + "github.com/jaegertracing/jaeger/thrift-gen/jaeger" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +var blankJaegerProtoSpan = new(jaeger.Span) + +// ProtoBatchesToInternalTraces converts multiple Jaeger proto batches to internal traces +func ProtoBatchesToInternalTraces(batches []*model.Batch) pdata.Traces { + traceData := pdata.NewTraces() + if len(batches) == 0 { + return traceData + } + + rss := traceData.ResourceSpans() + rss.Resize(len(batches)) + + i := 0 + for _, batch := range batches { + if batch.GetProcess() == nil && len(batch.GetSpans()) == 0 { + continue + } + + protoBatchToResourceSpans(*batch, rss.At(i)) + i++ + } + + // reduce traceData.ResourceSpans slice if some batched were skipped + if i < len(batches) { + rss.Resize(i) + } + + return traceData +} + +// ProtoBatchToInternalTraces converts Jeager proto batch to internal traces +func ProtoBatchToInternalTraces(batch model.Batch) pdata.Traces { + traceData := pdata.NewTraces() + + if batch.GetProcess() == nil && len(batch.GetSpans()) == 0 { + return traceData + } + + rss := traceData.ResourceSpans() + rss.Resize(1) + protoBatchToResourceSpans(batch, rss.At(0)) + + return traceData +} + +func protoBatchToResourceSpans(batch model.Batch, dest pdata.ResourceSpans) { + jSpans := batch.GetSpans() + + jProcessToInternalResource(batch.GetProcess(), dest.Resource()) + + if len(jSpans) == 0 { + return + } + + groupByLibrary := jSpansToInternal(jSpans) + ilss := dest.InstrumentationLibrarySpans() + for _, v := range groupByLibrary { + ilss.Append(v) + } +} + +func jProcessToInternalResource(process *model.Process, dest pdata.Resource) { + if process == nil || process.ServiceName == tracetranslator.ResourceNoServiceName { + return + } + + serviceName := process.ServiceName + tags := process.Tags + if serviceName == "" && tags == nil { + return + } + + attrs := dest.Attributes() + if serviceName != "" { + attrs.InitEmptyWithCapacity(len(tags) + 1) + attrs.UpsertString(conventions.AttributeServiceName, serviceName) + } else { + attrs.InitEmptyWithCapacity(len(tags)) + } + jTagsToInternalAttributes(tags, attrs) + + // Handle special keys translations. + translateHostnameAttr(attrs) + translateJaegerVersionAttr(attrs) +} + +// translateHostnameAttr translates "hostname" atttribute +func translateHostnameAttr(attrs pdata.AttributeMap) { + hostname, hostnameFound := attrs.Get("hostname") + _, convHostNameFound := attrs.Get(conventions.AttributeHostName) + if hostnameFound && !convHostNameFound { + attrs.Insert(conventions.AttributeHostName, hostname) + attrs.Delete("hostname") + } +} + +// translateHostnameAttr translates "jaeger.version" atttribute +func translateJaegerVersionAttr(attrs pdata.AttributeMap) { + jaegerVersion, jaegerVersionFound := attrs.Get("jaeger.version") + _, exporterVersionFound := attrs.Get(conventions.OCAttributeExporterVersion) + if jaegerVersionFound && !exporterVersionFound { + attrs.InsertString(conventions.OCAttributeExporterVersion, "Jaeger-"+jaegerVersion.StringVal()) + attrs.Delete("jaeger.version") + } +} + +func jSpansToInternal(spans []*model.Span) map[instrumentationLibrary]pdata.InstrumentationLibrarySpans { + spansByLibrary := make(map[instrumentationLibrary]pdata.InstrumentationLibrarySpans) + + for _, span := range spans { + if span == nil || reflect.DeepEqual(span, blankJaegerProtoSpan) { + continue + } + pSpan, library := jSpanToInternal(span) + ils, found := spansByLibrary[library] + if !found { + ils = pdata.NewInstrumentationLibrarySpans() + spansByLibrary[library] = ils + + if library.name != "" { + ils.InstrumentationLibrary().SetName(library.name) + ils.InstrumentationLibrary().SetVersion(library.version) + } + } + ils.Spans().Append(pSpan) + } + return spansByLibrary +} + +type instrumentationLibrary struct { + name, version string +} + +func jSpanToInternal(span *model.Span) (pdata.Span, instrumentationLibrary) { + dest := pdata.NewSpan() + dest.SetTraceID(tracetranslator.UInt64ToTraceID(span.TraceID.High, span.TraceID.Low)) + dest.SetSpanID(tracetranslator.UInt64ToSpanID(uint64(span.SpanID))) + dest.SetName(span.OperationName) + dest.SetStartTime(pdata.TimeToUnixNano(span.StartTime)) + dest.SetEndTime(pdata.TimeToUnixNano(span.StartTime.Add(span.Duration))) + + parentSpanID := span.ParentSpanID() + if parentSpanID != model.SpanID(0) { + dest.SetParentSpanID(tracetranslator.UInt64ToSpanID(uint64(parentSpanID))) + } + + attrs := dest.Attributes() + attrs.InitEmptyWithCapacity(len(span.Tags)) + jTagsToInternalAttributes(span.Tags, attrs) + setInternalSpanStatus(attrs, dest.Status()) + if spanKindAttr, ok := attrs.Get(tracetranslator.TagSpanKind); ok { + dest.SetKind(jSpanKindToInternal(spanKindAttr.StringVal())) + attrs.Delete(tracetranslator.TagSpanKind) + } + + il := instrumentationLibrary{} + if libraryName, ok := attrs.Get(tracetranslator.TagInstrumentationName); ok { + il.name = libraryName.StringVal() + attrs.Delete(tracetranslator.TagInstrumentationName) + if libraryVersion, ok := attrs.Get(tracetranslator.TagInstrumentationVersion); ok { + il.version = libraryVersion.StringVal() + attrs.Delete(tracetranslator.TagInstrumentationVersion) + } + } + + dest.SetTraceState(getTraceStateFromAttrs(attrs)) + + // drop the attributes slice if all of them were replaced during translation + if attrs.Len() == 0 { + attrs.InitFromMap(nil) + } + + jLogsToSpanEvents(span.Logs, dest.Events()) + jReferencesToSpanLinks(span.References, parentSpanID, dest.Links()) + + return dest, il +} + +func jTagsToInternalAttributes(tags []model.KeyValue, dest pdata.AttributeMap) { + for _, tag := range tags { + switch tag.GetVType() { + case model.ValueType_STRING: + dest.UpsertString(tag.Key, tag.GetVStr()) + case model.ValueType_BOOL: + dest.UpsertBool(tag.Key, tag.GetVBool()) + case model.ValueType_INT64: + dest.UpsertInt(tag.Key, tag.GetVInt64()) + case model.ValueType_FLOAT64: + dest.UpsertDouble(tag.Key, tag.GetVFloat64()) + case model.ValueType_BINARY: + dest.UpsertString(tag.Key, base64.StdEncoding.EncodeToString(tag.GetVBinary())) + default: + dest.UpsertString(tag.Key, fmt.Sprintf("", tag.GetVType())) + } + } +} + +func setInternalSpanStatus(attrs pdata.AttributeMap, dest pdata.SpanStatus) { + + statusCode := pdata.StatusCodeUnset + statusMessage := "" + statusExists := false + + if errorVal, ok := attrs.Get(tracetranslator.TagError); ok { + if errorVal.BoolVal() { + statusCode = pdata.StatusCodeError + attrs.Delete(tracetranslator.TagError) + statusExists = true + } + } + + if codeAttr, ok := attrs.Get(tracetranslator.TagStatusCode); ok { + statusExists = true + if code, err := getStatusCodeValFromAttr(codeAttr); err == nil { + statusCode = pdata.StatusCode(code) + attrs.Delete(tracetranslator.TagStatusCode) + } + if msgAttr, ok := attrs.Get(tracetranslator.TagStatusMsg); ok { + statusMessage = msgAttr.StringVal() + attrs.Delete(tracetranslator.TagStatusMsg) + } + } else if httpCodeAttr, ok := attrs.Get(tracetranslator.TagHTTPStatusCode); ok { + statusExists = true + if code, err := getStatusCodeFromHTTPStatusAttr(httpCodeAttr); err == nil { + + // Do not set status code in case it was set to Unset. + if code != pdata.StatusCodeUnset { + statusCode = code + } + + if msgAttr, ok := attrs.Get(tracetranslator.TagHTTPStatusMsg); ok { + statusMessage = msgAttr.StringVal() + } + } + } + + if statusExists { + dest.SetCode(statusCode) + dest.SetMessage(statusMessage) + } +} + +func getStatusCodeValFromAttr(attrVal pdata.AttributeValue) (int, error) { + var codeVal int64 + switch attrVal.Type() { + case pdata.AttributeValueINT: + codeVal = attrVal.IntVal() + case pdata.AttributeValueSTRING: + i, err := strconv.Atoi(attrVal.StringVal()) + if err != nil { + return 0, err + } + codeVal = int64(i) + default: + return 0, fmt.Errorf("invalid status code attribute type: %s", attrVal.Type().String()) + } + if codeVal > math.MaxInt32 || codeVal < math.MinInt32 { + return 0, fmt.Errorf("invalid status code value: %d", codeVal) + } + return int(codeVal), nil +} + +func getStatusCodeFromHTTPStatusAttr(attrVal pdata.AttributeValue) (pdata.StatusCode, error) { + statusCode, err := getStatusCodeValFromAttr(attrVal) + if err != nil { + return pdata.StatusCodeOk, err + } + + return tracetranslator.StatusCodeFromHTTP(statusCode), nil +} + +func jSpanKindToInternal(spanKind string) pdata.SpanKind { + switch spanKind { + case "client": + return pdata.SpanKindCLIENT + case "server": + return pdata.SpanKindSERVER + case "producer": + return pdata.SpanKindPRODUCER + case "consumer": + return pdata.SpanKindCONSUMER + case "internal": + return pdata.SpanKindINTERNAL + } + return pdata.SpanKindUNSPECIFIED +} + +func jLogsToSpanEvents(logs []model.Log, dest pdata.SpanEventSlice) { + if len(logs) == 0 { + return + } + + dest.Resize(len(logs)) + + for i, log := range logs { + event := dest.At(i) + + event.SetTimestamp(pdata.TimestampUnixNano(uint64(log.Timestamp.UnixNano()))) + if len(log.Fields) == 0 { + continue + } + + attrs := event.Attributes() + attrs.InitEmptyWithCapacity(len(log.Fields)) + jTagsToInternalAttributes(log.Fields, attrs) + if name, ok := attrs.Get(tracetranslator.TagMessage); ok { + event.SetName(name.StringVal()) + attrs.Delete(tracetranslator.TagMessage) + } + } +} + +// jReferencesToSpanLinks sets internal span links based on jaeger span references skipping excludeParentID +func jReferencesToSpanLinks(refs []model.SpanRef, excludeParentID model.SpanID, dest pdata.SpanLinkSlice) { + if len(refs) == 0 || len(refs) == 1 && refs[0].SpanID == excludeParentID && refs[0].RefType == model.ChildOf { + return + } + + dest.Resize(len(refs)) + i := 0 + for _, ref := range refs { + link := dest.At(i) + if ref.SpanID == excludeParentID && ref.RefType == model.ChildOf { + continue + } + + link.SetTraceID(tracetranslator.UInt64ToTraceID(ref.TraceID.High, ref.TraceID.Low)) + link.SetSpanID(pdata.NewSpanID(tracetranslator.UInt64ToByteSpanID(uint64(ref.SpanID)))) + i++ + } + + // Reduce slice size in case if excludeParentID was skipped + if i < len(refs) { + dest.Resize(i) + } +} + +func getTraceStateFromAttrs(attrs pdata.AttributeMap) pdata.TraceState { + traceState := pdata.TraceStateEmpty + // TODO Bring this inline with solution for jaegertracing/jaeger-client-java #702 once available + if attr, ok := attrs.Get(tracetranslator.TagW3CTraceState); ok { + traceState = pdata.TraceState(attr.StringVal()) + attrs.Delete(tracetranslator.TagW3CTraceState) + } + return traceState +} diff --git a/internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces_test.go b/internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces_test.go new file mode 100644 index 00000000000..947f8c25e6f --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/jaegerproto_to_traces_test.go @@ -0,0 +1,865 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "encoding/binary" + "fmt" + "strconv" + "testing" + "time" + + "github.com/jaegertracing/jaeger/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +// Use timespamp with microsecond granularity to work well with jaeger thrift translation +var ( + testSpanStartTime = time.Date(2020, 2, 11, 20, 26, 12, 321000, time.UTC) + testSpanStartTimestamp = pdata.TimestampUnixNano(testSpanStartTime.UnixNano()) + testSpanEventTime = time.Date(2020, 2, 11, 20, 26, 13, 123000, time.UTC) + testSpanEventTimestamp = pdata.TimestampUnixNano(testSpanEventTime.UnixNano()) + testSpanEndTime = time.Date(2020, 2, 11, 20, 26, 13, 789000, time.UTC) + testSpanEndTimestamp = pdata.TimestampUnixNano(testSpanEndTime.UnixNano()) +) + +func TestGetStatusCodeValFromAttr(t *testing.T) { + _, invalidNumErr := strconv.Atoi("inf") + + tests := []struct { + name string + attr pdata.AttributeValue + code int + err error + }{ + { + name: "ok-string", + attr: pdata.NewAttributeValueString("0"), + code: 0, + err: nil, + }, + + { + name: "ok-int", + attr: pdata.NewAttributeValueInt(1), + code: 1, + err: nil, + }, + + { + name: "wrong-type", + attr: pdata.NewAttributeValueBool(true), + code: 0, + err: fmt.Errorf("invalid status code attribute type: BOOL"), + }, + + { + name: "invalid-string", + attr: pdata.NewAttributeValueString("inf"), + code: 0, + err: invalidNumErr, + }, + + { + name: "invalid-int", + attr: pdata.NewAttributeValueInt(1844674407370955), + code: 0, + err: fmt.Errorf("invalid status code value: 1844674407370955"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + code, err := getStatusCodeValFromAttr(test.attr) + assert.EqualValues(t, test.err, err) + assert.Equal(t, test.code, code) + }) + } +} + +func TestGetStatusCodeFromHTTPStatusAttr(t *testing.T) { + tests := []struct { + name string + attr pdata.AttributeValue + code pdata.StatusCode + }{ + { + name: "string-unknown", + attr: pdata.NewAttributeValueString("10"), + code: pdata.StatusCodeError, + }, + + { + name: "string-ok", + attr: pdata.NewAttributeValueString("101"), + code: pdata.StatusCodeUnset, + }, + + { + name: "int-not-found", + attr: pdata.NewAttributeValueInt(404), + code: pdata.StatusCodeError, + }, + { + name: "int-invalid-arg", + attr: pdata.NewAttributeValueInt(408), + code: pdata.StatusCodeError, + }, + + { + name: "int-internal", + attr: pdata.NewAttributeValueInt(500), + code: pdata.StatusCodeError, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + code, err := getStatusCodeFromHTTPStatusAttr(test.attr) + assert.NoError(t, err) + assert.Equal(t, test.code, code) + }) + } +} + +func TestJTagsToInternalAttributes(t *testing.T) { + tags := []model.KeyValue{ + { + Key: "bool-val", + VType: model.ValueType_BOOL, + VBool: true, + }, + { + Key: "int-val", + VType: model.ValueType_INT64, + VInt64: 123, + }, + { + Key: "string-val", + VType: model.ValueType_STRING, + VStr: "abc", + }, + { + Key: "double-val", + VType: model.ValueType_FLOAT64, + VFloat64: 1.23, + }, + { + Key: "binary-val", + VType: model.ValueType_BINARY, + VBinary: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7D, 0x98}, + }, + } + + expected := pdata.NewAttributeMap() + expected.InsertBool("bool-val", true) + expected.InsertInt("int-val", 123) + expected.InsertString("string-val", "abc") + expected.InsertDouble("double-val", 1.23) + expected.InsertString("binary-val", "AAAAAABkfZg=") + + got := pdata.NewAttributeMap() + jTagsToInternalAttributes(tags, got) + + require.EqualValues(t, expected, got) +} + +func TestProtoBatchToInternalTraces(t *testing.T) { + + tests := []struct { + name string + jb model.Batch + td pdata.Traces + }{ + { + name: "empty", + jb: model.Batch{}, + td: testdata.GenerateTraceDataEmpty(), + }, + + { + name: "no-spans", + jb: model.Batch{ + Process: generateProtoProcess(), + }, + td: generateTraceDataResourceOnly(), + }, + + { + name: "no-resource-attrs", + jb: model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + }, + td: generateTraceDataResourceOnlyWithNoAttrs(), + }, + + { + name: "one-span-no-resources", + jb: model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpanWithTraceState(), + }, + }, + td: generateTraceDataOneSpanNoResourceWithTraceState(), + }, + { + name: "two-spans-child-parent", + jb: model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpan(), + generateProtoChildSpan(), + }, + }, + td: generateTraceDataTwoSpansChildParent(), + }, + + { + name: "two-spans-with-follower", + jb: model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpan(), + generateProtoFollowerSpan(), + }, + }, + td: generateTraceDataTwoSpansWithFollower(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + td := ProtoBatchToInternalTraces(test.jb) + assert.EqualValues(t, test.td, td) + }) + } +} + +func TestProtoBatchToInternalTracesWithTwoLibraries(t *testing.T) { + jb := model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + { + StartTime: testSpanStartTime, + Duration: testSpanEndTime.Sub(testSpanStartTime), + OperationName: "operation2", + Tags: []model.KeyValue{ + { + Key: tracetranslator.TagInstrumentationName, + VType: model.ValueType_STRING, + VStr: "library2", + }, { + Key: tracetranslator.TagInstrumentationVersion, + VType: model.ValueType_STRING, + VStr: "0.42.0", + }, + }, + }, + { + TraceID: model.NewTraceID(0, 0), + StartTime: testSpanStartTime, + Duration: testSpanEndTime.Sub(testSpanStartTime), + OperationName: "operation1", + Tags: []model.KeyValue{ + { + Key: tracetranslator.TagInstrumentationName, + VType: model.ValueType_STRING, + VStr: "library1", + }, { + Key: tracetranslator.TagInstrumentationVersion, + VType: model.ValueType_STRING, + VStr: "0.42.0", + }, + }, + }, + }, + } + expected := generateTraceDataTwoSpansFromTwoLibraries() + library1Span := expected.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0) + library2Span := expected.ResourceSpans().At(0).InstrumentationLibrarySpans().At(1) + + actual := ProtoBatchToInternalTraces(jb) + + assert.Equal(t, actual.ResourceSpans().Len(), 1) + assert.Equal(t, actual.ResourceSpans().At(0).InstrumentationLibrarySpans().Len(), 2) + + ils0 := actual.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0) + ils1 := actual.ResourceSpans().At(0).InstrumentationLibrarySpans().At(1) + if ils0.InstrumentationLibrary().Name() == "library1" { + assert.EqualValues(t, library1Span, ils0) + assert.EqualValues(t, library2Span, ils1) + } else { + assert.EqualValues(t, library1Span, ils1) + assert.EqualValues(t, library2Span, ils0) + } +} + +func TestSetInternalSpanStatus(t *testing.T) { + + emptyStatus := pdata.NewSpanStatus() + + okStatus := pdata.NewSpanStatus() + okStatus.SetCode(pdata.StatusCodeOk) + + errorStatus := pdata.NewSpanStatus() + errorStatus.SetCode(pdata.StatusCodeError) + + errorStatusWithMessage := pdata.NewSpanStatus() + errorStatusWithMessage.SetCode(pdata.StatusCodeError) + errorStatusWithMessage.SetMessage("Error: Invalid argument") + + errorStatusWith404Message := pdata.NewSpanStatus() + errorStatusWith404Message.SetCode(pdata.StatusCodeError) + errorStatusWith404Message.SetMessage("HTTP 404: Not Found") + + tests := []struct { + name string + attrs pdata.AttributeMap + status pdata.SpanStatus + attrsModifiedLen int // Length of attributes map after dropping converted fields + }{ + { + name: "No tags set -> OK status", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{}), + status: emptyStatus, + attrsModifiedLen: 0, + }, + { + name: "error tag set -> Error status", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagError: pdata.NewAttributeValueBool(true), + }), + status: errorStatus, + attrsModifiedLen: 0, + }, + { + name: "status.code is set as int", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagStatusCode: pdata.NewAttributeValueInt(1), + }), + status: okStatus, + attrsModifiedLen: 0, + }, + { + name: "status.code, status.message and error tags are set", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagError: pdata.NewAttributeValueBool(true), + tracetranslator.TagStatusCode: pdata.NewAttributeValueInt(int64(pdata.StatusCodeError)), + tracetranslator.TagStatusMsg: pdata.NewAttributeValueString("Error: Invalid argument"), + }), + status: errorStatusWithMessage, + attrsModifiedLen: 0, + }, + { + name: "http.status_code tag is set as string", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagHTTPStatusCode: pdata.NewAttributeValueString("404"), + }), + status: errorStatus, + attrsModifiedLen: 1, + }, + { + name: "http.status_code, http.status_message and error tags are set", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagError: pdata.NewAttributeValueBool(true), + tracetranslator.TagHTTPStatusCode: pdata.NewAttributeValueInt(404), + tracetranslator.TagHTTPStatusMsg: pdata.NewAttributeValueString("HTTP 404: Not Found"), + }), + status: errorStatusWith404Message, + attrsModifiedLen: 2, + }, + { + name: "status.code has precedence over http.status_code.", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagStatusCode: pdata.NewAttributeValueInt(1), + tracetranslator.TagHTTPStatusCode: pdata.NewAttributeValueInt(500), + tracetranslator.TagHTTPStatusMsg: pdata.NewAttributeValueString("Server Error"), + }), + status: okStatus, + attrsModifiedLen: 2, + }, + { + name: "Ignore http.status_code == 200 if error set to true.", + attrs: pdata.NewAttributeMap().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagError: pdata.NewAttributeValueBool(true), + tracetranslator.TagHTTPStatusCode: pdata.NewAttributeValueInt(200), + }), + status: errorStatus, + attrsModifiedLen: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + status := pdata.NewSpanStatus() + setInternalSpanStatus(test.attrs, status) + assert.EqualValues(t, test.status, status) + assert.Equal(t, test.attrsModifiedLen, test.attrs.Len()) + }) + } +} + +func TestProtoBatchesToInternalTraces(t *testing.T) { + batches := []*model.Batch{ + { + Process: generateProtoProcess(), + Spans: []*model.Span{ + generateProtoSpan(), + }, + }, + { + Spans: []*model.Span{ + generateProtoSpan(), + generateProtoChildSpan(), + }, + }, + { + // should be skipped + Spans: []*model.Span{}, + }, + } + + expected := generateTraceDataOneSpanNoResource() + resource := generateTraceDataResourceOnly().ResourceSpans().At(0).Resource() + resource.CopyTo(expected.ResourceSpans().At(0).Resource()) + expected.ResourceSpans().Resize(2) + twoSpans := generateTraceDataTwoSpansChildParent().ResourceSpans().At(0) + twoSpans.CopyTo(expected.ResourceSpans().At(1)) + + got := ProtoBatchesToInternalTraces(batches) + assert.EqualValues(t, expected, got) +} + +func TestJSpanKindToInternal(t *testing.T) { + tests := []struct { + jSpanKind string + otlpSpanKind pdata.SpanKind + }{ + { + jSpanKind: "client", + otlpSpanKind: pdata.SpanKindCLIENT, + }, + { + jSpanKind: "server", + otlpSpanKind: pdata.SpanKindSERVER, + }, + { + jSpanKind: "producer", + otlpSpanKind: pdata.SpanKindPRODUCER, + }, + { + jSpanKind: "consumer", + otlpSpanKind: pdata.SpanKindCONSUMER, + }, + { + jSpanKind: "internal", + otlpSpanKind: pdata.SpanKindINTERNAL, + }, + { + jSpanKind: "all-others", + otlpSpanKind: pdata.SpanKindUNSPECIFIED, + }, + } + + for _, test := range tests { + t.Run(test.jSpanKind, func(t *testing.T) { + assert.Equal(t, test.otlpSpanKind, jSpanKindToInternal(test.jSpanKind)) + }) + } +} + +func generateTraceDataResourceOnly() pdata.Traces { + td := testdata.GenerateTraceDataOneEmptyResourceSpans() + rs := td.ResourceSpans().At(0).Resource() + rs.Attributes().InsertString(conventions.AttributeServiceName, "service-1") + rs.Attributes().InsertInt("int-attr-1", 123) + return td +} + +func generateTraceDataResourceOnlyWithNoAttrs() pdata.Traces { + td := testdata.GenerateTraceDataOneEmptyResourceSpans() + td.ResourceSpans().At(0).Resource().Attributes().InitFromMap(map[string]pdata.AttributeValue{}) + return td +} + +func generateProtoProcess() *model.Process { + return &model.Process{ + ServiceName: "service-1", + Tags: []model.KeyValue{ + { + Key: "int-attr-1", + VType: model.ValueType_INT64, + VInt64: 123, + }, + }, + } +} + +func generateTraceDataOneSpanNoResource() pdata.Traces { + td := testdata.GenerateTraceDataOneSpanNoResource() + span := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + span.SetSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})) + span.SetTraceID(pdata.NewTraceID( + [16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})) + span.SetDroppedAttributesCount(0) + span.SetDroppedEventsCount(0) + span.SetStartTime(testSpanStartTimestamp) + span.SetEndTime(testSpanEndTimestamp) + span.SetKind(pdata.SpanKindCLIENT) + span.Events().At(0).SetTimestamp(testSpanEventTimestamp) + span.Events().At(0).SetDroppedAttributesCount(0) + span.Events().At(0).SetName("event-with-attr") + span.Events().At(1).SetTimestamp(testSpanEventTimestamp) + span.Events().At(1).SetDroppedAttributesCount(0) + span.Events().At(1).SetName("") + span.Events().At(1).Attributes().InsertInt("attr-int", 123) + return td +} + +func generateTraceDataWithLibraryInfo() pdata.Traces { + td := generateTraceDataOneSpanNoResource() + rs0 := td.ResourceSpans().At(0) + rs0ils0 := rs0.InstrumentationLibrarySpans().At(0) + rs0ils0.InstrumentationLibrary().SetName("io.opentelemetry.test") + rs0ils0.InstrumentationLibrary().SetVersion("0.42.0") + return td +} + +func generateTraceDataOneSpanNoResourceWithTraceState() pdata.Traces { + td := generateTraceDataOneSpanNoResource() + span := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + span.SetTraceState("lasterror=f39cd56cc44274fd5abd07ef1164246d10ce2955") + return td +} + +func generateProtoSpan() *model.Span { + return &model.Span{ + TraceID: model.NewTraceID( + binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}), + binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}), + ), + SpanID: model.NewSpanID(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + OperationName: "operationA", + StartTime: testSpanStartTime, + Duration: testSpanEndTime.Sub(testSpanStartTime), + Logs: []model.Log{ + { + Timestamp: testSpanEventTime, + Fields: []model.KeyValue{ + { + Key: tracetranslator.TagMessage, + VType: model.ValueType_STRING, + VStr: "event-with-attr", + }, + { + Key: "span-event-attr", + VType: model.ValueType_STRING, + VStr: "span-event-attr-val", + }, + }, + }, + { + Timestamp: testSpanEventTime, + Fields: []model.KeyValue{ + { + Key: "attr-int", + VType: model.ValueType_INT64, + VInt64: 123, + }, + }, + }, + }, + Tags: []model.KeyValue{ + { + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindClient), + }, + { + Key: tracetranslator.TagStatusCode, + VType: model.ValueType_INT64, + VInt64: int64(pdata.StatusCodeError), + }, + { + Key: tracetranslator.TagError, + VBool: true, + VType: model.ValueType_BOOL, + }, + { + Key: tracetranslator.TagStatusMsg, + VType: model.ValueType_STRING, + VStr: "status-cancelled", + }, + }, + } +} + +func generateProtoSpanWithLibraryInfo(libraryName string) *model.Span { + span := generateProtoSpan() + span.Tags = append([]model.KeyValue{ + { + Key: tracetranslator.TagInstrumentationName, + VType: model.ValueType_STRING, + VStr: libraryName, + }, { + Key: tracetranslator.TagInstrumentationVersion, + VType: model.ValueType_STRING, + VStr: "0.42.0", + }, + }, span.Tags...) + + return span +} +func generateProtoSpanWithTraceState() *model.Span { + return &model.Span{ + TraceID: model.NewTraceID( + binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}), + binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}), + ), + SpanID: model.NewSpanID(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + OperationName: "operationA", + StartTime: testSpanStartTime, + Duration: testSpanEndTime.Sub(testSpanStartTime), + Logs: []model.Log{ + { + Timestamp: testSpanEventTime, + Fields: []model.KeyValue{ + { + Key: tracetranslator.TagMessage, + VType: model.ValueType_STRING, + VStr: "event-with-attr", + }, + { + Key: "span-event-attr", + VType: model.ValueType_STRING, + VStr: "span-event-attr-val", + }, + }, + }, + { + Timestamp: testSpanEventTime, + Fields: []model.KeyValue{ + { + Key: "attr-int", + VType: model.ValueType_INT64, + VInt64: 123, + }, + }, + }, + }, + Tags: []model.KeyValue{ + { + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindClient), + }, + { + Key: tracetranslator.TagStatusCode, + VType: model.ValueType_INT64, + VInt64: int64(pdata.StatusCodeError), + }, + { + Key: tracetranslator.TagError, + VBool: true, + VType: model.ValueType_BOOL, + }, + { + Key: tracetranslator.TagStatusMsg, + VType: model.ValueType_STRING, + VStr: "status-cancelled", + }, + { + Key: tracetranslator.TagW3CTraceState, + VType: model.ValueType_STRING, + VStr: "lasterror=f39cd56cc44274fd5abd07ef1164246d10ce2955", + }, + }, + } +} + +func generateTraceDataTwoSpansChildParent() pdata.Traces { + td := generateTraceDataOneSpanNoResource() + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + spans.Resize(2) + + span := spans.At(1) + span.SetName("operationB") + span.SetSpanID(pdata.NewSpanID([8]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})) + span.SetParentSpanID(spans.At(0).SpanID()) + span.SetKind(pdata.SpanKindSERVER) + span.SetTraceID(spans.At(0).TraceID()) + span.SetStartTime(spans.At(0).StartTime()) + span.SetEndTime(spans.At(0).EndTime()) + span.Status().SetCode(pdata.StatusCodeError) + span.Attributes().InitFromMap(map[string]pdata.AttributeValue{ + tracetranslator.TagHTTPStatusCode: pdata.NewAttributeValueInt(404), + }) + + return td +} + +func generateProtoChildSpan() *model.Span { + traceID := model.NewTraceID( + binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}), + binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}), + ) + return &model.Span{ + TraceID: traceID, + SpanID: model.NewSpanID(binary.BigEndian.Uint64([]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})), + OperationName: "operationB", + StartTime: testSpanStartTime, + Duration: testSpanEndTime.Sub(testSpanStartTime), + Tags: []model.KeyValue{ + { + Key: tracetranslator.TagHTTPStatusCode, + VType: model.ValueType_INT64, + VInt64: 404, + }, + { + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindServer), + }, + }, + References: []model.SpanRef{ + { + TraceID: traceID, + SpanID: model.NewSpanID(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + RefType: model.SpanRefType_CHILD_OF, + }, + }, + } +} + +func generateTraceDataTwoSpansWithFollower() pdata.Traces { + td := generateTraceDataOneSpanNoResource() + spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans() + spans.Resize(2) + + span := spans.At(1) + span.SetName("operationC") + span.SetSpanID(pdata.NewSpanID([8]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})) + span.SetTraceID(spans.At(0).TraceID()) + span.SetStartTime(spans.At(0).EndTime()) + span.SetEndTime(spans.At(0).EndTime() + 1000000) + span.SetKind(pdata.SpanKindCONSUMER) + span.Status().SetCode(pdata.StatusCodeOk) + span.Status().SetMessage("status-ok") + span.Links().Resize(1) + span.Links().At(0).SetTraceID(span.TraceID()) + span.Links().At(0).SetSpanID(spans.At(0).SpanID()) + return td +} + +func generateProtoFollowerSpan() *model.Span { + traceID := model.NewTraceID( + binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}), + binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}), + ) + return &model.Span{ + TraceID: traceID, + SpanID: model.NewSpanID(binary.BigEndian.Uint64([]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})), + OperationName: "operationC", + StartTime: testSpanEndTime, + Duration: time.Millisecond, + Tags: []model.KeyValue{ + { + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindConsumer), + }, + { + Key: tracetranslator.TagStatusCode, + VType: model.ValueType_INT64, + VInt64: int64(pdata.StatusCodeOk), + }, + { + Key: tracetranslator.TagStatusMsg, + VType: model.ValueType_STRING, + VStr: "status-ok", + }, + }, + References: []model.SpanRef{ + { + TraceID: traceID, + SpanID: model.NewSpanID(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + RefType: model.SpanRefType_FOLLOWS_FROM, + }, + }, + } +} + +func BenchmarkProtoBatchToInternalTraces(b *testing.B) { + jb := model.Batch{ + Process: generateProtoProcess(), + Spans: []*model.Span{ + generateProtoSpan(), + generateProtoChildSpan(), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + ProtoBatchToInternalTraces(jb) + } +} + +func generateTraceDataTwoSpansFromTwoLibraries() pdata.Traces { + td := testdata.GenerateTraceDataOneEmptyResourceSpans() + + rs0 := td.ResourceSpans().At(0) + rs0.InstrumentationLibrarySpans().Resize(2) + + rs0ils0 := rs0.InstrumentationLibrarySpans().At(0) + rs0ils0.InstrumentationLibrary().SetName("library1") + rs0ils0.InstrumentationLibrary().SetVersion("0.42.0") + rs0ils0.Spans().Resize(1) + span1 := rs0ils0.Spans().At(0) + span1.SetTraceID(pdata.NewTraceID(tracetranslator.UInt64ToByteTraceID(0, 0))) + span1.SetSpanID(pdata.NewSpanID(tracetranslator.UInt64ToByteSpanID(0))) + span1.SetName("operation1") + span1.SetStartTime(testSpanStartTimestamp) + span1.SetEndTime(testSpanEndTimestamp) + + rs0ils1 := rs0.InstrumentationLibrarySpans().At(1) + rs0ils1.InstrumentationLibrary().SetName("library2") + rs0ils1.InstrumentationLibrary().SetVersion("0.42.0") + rs0ils1.Spans().Resize(1) + span2 := rs0ils1.Spans().At(0) + span2.SetTraceID(span1.TraceID()) + span2.SetSpanID(span1.SpanID()) + span2.SetName("operation2") + span2.SetStartTime(testSpanStartTimestamp) + span2.SetEndTime(testSpanEndTimestamp) + + return td +} diff --git a/internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces.go b/internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces.go new file mode 100644 index 00000000000..c6aa7d21977 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces.go @@ -0,0 +1,201 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "encoding/base64" + "fmt" + "reflect" + + "github.com/jaegertracing/jaeger/thrift-gen/jaeger" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func ThriftBatchToInternalTraces(batch *jaeger.Batch) pdata.Traces { + traceData := pdata.NewTraces() + jProcess := batch.GetProcess() + jSpans := batch.GetSpans() + + if jProcess == nil && len(jSpans) == 0 { + return traceData + } + + rss := traceData.ResourceSpans() + rss.Resize(1) + rs := rss.At(0) + jThriftProcessToInternalResource(jProcess, rs.Resource()) + + if len(jSpans) == 0 { + return traceData + } + + ilss := rs.InstrumentationLibrarySpans() + ilss.Resize(1) + jThriftSpansToInternal(jSpans, ilss.At(0).Spans()) + + return traceData +} + +func jThriftProcessToInternalResource(process *jaeger.Process, dest pdata.Resource) { + if process == nil { + return + } + + serviceName := process.GetServiceName() + tags := process.GetTags() + if serviceName == "" && tags == nil { + return + } + + attrs := dest.Attributes() + if serviceName != "" { + attrs.InitEmptyWithCapacity(len(tags) + 1) + attrs.UpsertString(conventions.AttributeServiceName, serviceName) + } else { + attrs.InitEmptyWithCapacity(len(tags)) + } + jThriftTagsToInternalAttributes(tags, attrs) + + // Handle special keys translations. + translateHostnameAttr(attrs) + translateJaegerVersionAttr(attrs) +} + +func jThriftSpansToInternal(spans []*jaeger.Span, dest pdata.SpanSlice) { + if len(spans) == 0 { + return + } + + dest.Resize(len(spans)) + i := 0 + for _, span := range spans { + if span == nil || reflect.DeepEqual(span, blankJaegerProtoSpan) { + continue + } + jThriftSpanToInternal(span, dest.At(i)) + i++ + } + + if i < len(spans) { + dest.Resize(i) + } +} + +func jThriftSpanToInternal(span *jaeger.Span, dest pdata.Span) { + dest.SetTraceID(tracetranslator.Int64ToTraceID(span.TraceIdHigh, span.TraceIdLow)) + dest.SetSpanID(tracetranslator.Int64ToSpanID(span.SpanId)) + dest.SetName(span.OperationName) + dest.SetStartTime(microsecondsToUnixNano(span.StartTime)) + dest.SetEndTime(microsecondsToUnixNano(span.StartTime + span.Duration)) + + parentSpanID := span.ParentSpanId + if parentSpanID != 0 { + dest.SetParentSpanID(tracetranslator.Int64ToSpanID(parentSpanID)) + } + + attrs := dest.Attributes() + attrs.InitEmptyWithCapacity(len(span.Tags)) + jThriftTagsToInternalAttributes(span.Tags, attrs) + setInternalSpanStatus(attrs, dest.Status()) + if spanKindAttr, ok := attrs.Get(tracetranslator.TagSpanKind); ok { + dest.SetKind(jSpanKindToInternal(spanKindAttr.StringVal())) + attrs.Delete(tracetranslator.TagSpanKind) + } + + // drop the attributes slice if all of them were replaced during translation + if attrs.Len() == 0 { + attrs.InitFromMap(nil) + } + + jThriftLogsToSpanEvents(span.Logs, dest.Events()) + jThriftReferencesToSpanLinks(span.References, parentSpanID, dest.Links()) +} + +// jThriftTagsToInternalAttributes sets internal span links based on jaeger span references skipping excludeParentID +func jThriftTagsToInternalAttributes(tags []*jaeger.Tag, dest pdata.AttributeMap) { + for _, tag := range tags { + switch tag.GetVType() { + case jaeger.TagType_STRING: + dest.UpsertString(tag.Key, tag.GetVStr()) + case jaeger.TagType_BOOL: + dest.UpsertBool(tag.Key, tag.GetVBool()) + case jaeger.TagType_LONG: + dest.UpsertInt(tag.Key, tag.GetVLong()) + case jaeger.TagType_DOUBLE: + dest.UpsertDouble(tag.Key, tag.GetVDouble()) + case jaeger.TagType_BINARY: + dest.UpsertString(tag.Key, base64.StdEncoding.EncodeToString(tag.GetVBinary())) + default: + dest.UpsertString(tag.Key, fmt.Sprintf("", tag.GetVType())) + } + } +} + +func jThriftLogsToSpanEvents(logs []*jaeger.Log, dest pdata.SpanEventSlice) { + if len(logs) == 0 { + return + } + + dest.Resize(len(logs)) + + for i, log := range logs { + event := dest.At(i) + + event.SetTimestamp(microsecondsToUnixNano(log.Timestamp)) + if len(log.Fields) == 0 { + continue + } + + attrs := event.Attributes() + attrs.InitEmptyWithCapacity(len(log.Fields)) + jThriftTagsToInternalAttributes(log.Fields, attrs) + if name, ok := attrs.Get(tracetranslator.TagMessage); ok { + event.SetName(name.StringVal()) + attrs.Delete(tracetranslator.TagMessage) + } + } +} + +func jThriftReferencesToSpanLinks(refs []*jaeger.SpanRef, excludeParentID int64, dest pdata.SpanLinkSlice) { + if len(refs) == 0 || len(refs) == 1 && refs[0].SpanId == excludeParentID && refs[0].RefType == jaeger.SpanRefType_CHILD_OF { + return + } + + dest.Resize(len(refs)) + i := 0 + for _, ref := range refs { + link := dest.At(i) + if ref.SpanId == excludeParentID && ref.RefType == jaeger.SpanRefType_CHILD_OF { + continue + } + + link.SetTraceID(tracetranslator.Int64ToTraceID(ref.TraceIdHigh, ref.TraceIdLow)) + link.SetSpanID(pdata.NewSpanID(tracetranslator.Int64ToByteSpanID(ref.SpanId))) + i++ + } + + // Reduce slice size in case if excludeParentID was skipped + if i < len(refs) { + dest.Resize(i) + } +} + +// microsecondsToUnixNano converts epoch microseconds to pdata.TimestampUnixNano +func microsecondsToUnixNano(ms int64) pdata.TimestampUnixNano { + return pdata.TimestampUnixNano(uint64(ms) * 1000) +} diff --git a/internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces_test.go b/internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces_test.go new file mode 100644 index 00000000000..9f668ccfdcd --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/jaegerthrift_to_traces_test.go @@ -0,0 +1,301 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "encoding/binary" + "testing" + + "github.com/jaegertracing/jaeger/thrift-gen/jaeger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestJThriftTagsToInternalAttributes(t *testing.T) { + var intVal int64 = 123 + boolVal := true + stringVal := "abc" + doubleVal := 1.23 + tags := []*jaeger.Tag{ + { + Key: "bool-val", + VType: jaeger.TagType_BOOL, + VBool: &boolVal, + }, + { + Key: "int-val", + VType: jaeger.TagType_LONG, + VLong: &intVal, + }, + { + Key: "string-val", + VType: jaeger.TagType_STRING, + VStr: &stringVal, + }, + { + Key: "double-val", + VType: jaeger.TagType_DOUBLE, + VDouble: &doubleVal, + }, + { + Key: "binary-val", + VType: jaeger.TagType_BINARY, + VBinary: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7D, 0x98}, + }, + } + + expected := pdata.NewAttributeMap() + expected.InsertBool("bool-val", true) + expected.InsertInt("int-val", 123) + expected.InsertString("string-val", "abc") + expected.InsertDouble("double-val", 1.23) + expected.InsertString("binary-val", "AAAAAABkfZg=") + + got := pdata.NewAttributeMap() + jThriftTagsToInternalAttributes(tags, got) + + require.EqualValues(t, expected, got) +} + +func TestThriftBatchToInternalTraces(t *testing.T) { + + tests := []struct { + name string + jb *jaeger.Batch + td pdata.Traces + }{ + { + name: "empty", + jb: &jaeger.Batch{}, + td: testdata.GenerateTraceDataEmpty(), + }, + + { + name: "no-spans", + jb: &jaeger.Batch{ + Process: generateThriftProcess(), + }, + td: testdata.GenerateTraceDataNoLibraries(), + }, + + { + name: "one-span-no-resources", + jb: &jaeger.Batch{ + Spans: []*jaeger.Span{ + generateThriftSpan(), + }, + }, + td: generateTraceDataOneSpanNoResource(), + }, + { + name: "two-spans-child-parent", + jb: &jaeger.Batch{ + Spans: []*jaeger.Span{ + generateThriftSpan(), + generateThriftChildSpan(), + }, + }, + td: generateTraceDataTwoSpansChildParent(), + }, + + { + name: "two-spans-with-follower", + jb: &jaeger.Batch{ + Spans: []*jaeger.Span{ + generateThriftSpan(), + generateThriftFollowerSpan(), + }, + }, + td: generateTraceDataTwoSpansWithFollower(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + td := ThriftBatchToInternalTraces(test.jb) + assert.EqualValues(t, test.td, td) + }) + } +} + +func generateThriftProcess() *jaeger.Process { + attrVal := "resource-attr-val-1" + return &jaeger.Process{ + Tags: []*jaeger.Tag{ + { + Key: "resource-attr", + VType: jaeger.TagType_STRING, + VStr: &attrVal, + }, + }, + } +} + +func generateThriftSpan() *jaeger.Span { + spanStartTs := unixNanoToMicroseconds(testSpanStartTimestamp) + spanEndTs := unixNanoToMicroseconds(testSpanEndTimestamp) + eventTs := unixNanoToMicroseconds(testSpanEventTimestamp) + intAttrVal := int64(123) + eventName := "event-with-attr" + eventStrAttrVal := "span-event-attr-val" + statusCode := int64(pdata.StatusCodeError) + statusMsg := "status-cancelled" + kind := string(tracetranslator.OpenTracingSpanKindClient) + + return &jaeger.Span{ + TraceIdHigh: int64(binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8})), + TraceIdLow: int64(binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})), + SpanId: int64(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + OperationName: "operationA", + StartTime: spanStartTs, + Duration: spanEndTs - spanStartTs, + Logs: []*jaeger.Log{ + { + Timestamp: eventTs, + Fields: []*jaeger.Tag{ + { + Key: tracetranslator.TagMessage, + VType: jaeger.TagType_STRING, + VStr: &eventName, + }, + { + Key: "span-event-attr", + VType: jaeger.TagType_STRING, + VStr: &eventStrAttrVal, + }, + }, + }, + { + Timestamp: eventTs, + Fields: []*jaeger.Tag{ + { + Key: "attr-int", + VType: jaeger.TagType_LONG, + VLong: &intAttrVal, + }, + }, + }, + }, + Tags: []*jaeger.Tag{ + { + Key: tracetranslator.TagStatusCode, + VType: jaeger.TagType_LONG, + VLong: &statusCode, + }, + { + Key: tracetranslator.TagStatusMsg, + VType: jaeger.TagType_STRING, + VStr: &statusMsg, + }, + { + Key: tracetranslator.TagSpanKind, + VType: jaeger.TagType_STRING, + VStr: &kind, + }, + }, + } +} + +func generateThriftChildSpan() *jaeger.Span { + spanStartTs := unixNanoToMicroseconds(testSpanStartTimestamp) + spanEndTs := unixNanoToMicroseconds(testSpanEndTimestamp) + notFoundAttrVal := int64(404) + kind := string(tracetranslator.OpenTracingSpanKindServer) + + return &jaeger.Span{ + TraceIdHigh: int64(binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8})), + TraceIdLow: int64(binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})), + SpanId: int64(binary.BigEndian.Uint64([]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})), + ParentSpanId: int64(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + OperationName: "operationB", + StartTime: spanStartTs, + Duration: spanEndTs - spanStartTs, + Tags: []*jaeger.Tag{ + { + Key: tracetranslator.TagHTTPStatusCode, + VType: jaeger.TagType_LONG, + VLong: ¬FoundAttrVal, + }, + { + Key: tracetranslator.TagSpanKind, + VType: jaeger.TagType_STRING, + VStr: &kind, + }, + }, + } +} + +func generateThriftFollowerSpan() *jaeger.Span { + statusCode := int64(pdata.StatusCodeOk) + statusMsg := "status-ok" + kind := string(tracetranslator.OpenTracingSpanKindConsumer) + + return &jaeger.Span{ + TraceIdHigh: int64(binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8})), + TraceIdLow: int64(binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})), + SpanId: int64(binary.BigEndian.Uint64([]byte{0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18})), + OperationName: "operationC", + StartTime: unixNanoToMicroseconds(testSpanEndTimestamp), + Duration: 1000, + Tags: []*jaeger.Tag{ + { + Key: tracetranslator.TagStatusCode, + VType: jaeger.TagType_LONG, + VLong: &statusCode, + }, + { + Key: tracetranslator.TagStatusMsg, + VType: jaeger.TagType_STRING, + VStr: &statusMsg, + }, + { + Key: tracetranslator.TagSpanKind, + VType: jaeger.TagType_STRING, + VStr: &kind, + }, + }, + References: []*jaeger.SpanRef{ + { + TraceIdHigh: int64(binary.BigEndian.Uint64([]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8})), + TraceIdLow: int64(binary.BigEndian.Uint64([]byte{0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})), + SpanId: int64(binary.BigEndian.Uint64([]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + RefType: jaeger.SpanRefType_FOLLOWS_FROM, + }, + }, + } +} + +func unixNanoToMicroseconds(ns pdata.TimestampUnixNano) int64 { + return int64(ns / 1000) +} + +func BenchmarkThriftBatchToInternalTraces(b *testing.B) { + jb := &jaeger.Batch{ + Process: generateThriftProcess(), + Spans: []*jaeger.Span{ + generateThriftSpan(), + generateThriftChildSpan(), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + ThriftBatchToInternalTraces(jb) + } +} diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_01.json b/internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_01.json new file mode 100644 index 00000000000..ed7318b6f70 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_01.json @@ -0,0 +1,202 @@ +{ + "process": { + "service_name": "api", + "tags": [ + { + "key": "hostname", + "v_type": 0, + "v_str": "api246-sjc1" + }, + { + "key": "pid", + "v_type": 2, + "v_int64": 13 + }, + { + "key": "start.time", + "v_type": 0, + "v_str": "2017-01-26T21:46:30.639875Z" + }, + { + "key": "ip", + "v_type": 0, + "v_str": "10.53.69.61" + }, + { + "key": "opencensus.exporterversion", + "v_type": 0, + "v_str": "someVersion" + }, + { + "key": "a.bool", + "v_type": 0, + "v_str": "true" + }, + { + "key": "a.double", + "v_type": 0, + "v_str": "1234.56789" + }, + { + "key": "a.long", + "v_type": 0, + "v_str": "123456789" + }, + { + "key": "a.binary", + "v_type": 0, + "v_str": "AQIDBAMCAQ==" + } + ] + }, + "spans": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "parent_span_id": 6866147, + "operation_name": "get", + "start_time": "2017-01-26T21:46:31.639875Z", + "duration": 22938000, + "tags": [ + { + "key": "http.url", + "v_type": 0, + "v_str": "http://localhost:15598/client_transactions" + }, + { + "key": "span.kind", + "v_type": 0, + "v_str": "server" + }, + { + "key": "peer.port", + "v_type": 2, + "v_int64": 53931 + }, + { + "key": "someBool", + "v_type": 1, + "v_bool": true + }, + { + "key": "someDouble", + "v_type": 3, + "v_float64": 129.8 + }, + { + "key": "peer.service", + "v_type": 0, + "v_str": "rtapi" + }, + { + "key": "peer.ipv4", + "v_type": 2, + "v_int64": 3224716605 + } + ], + "logs": [ + { + "timestamp": "2017-01-26T21:46:31.639874Z", + "fields": [ + { + "key": "message.id", + "v_int64": 0, + "v_type": 2 + }, + { + "key": "message.type", + "v_str": "SENT", + "v_type": 0 + }, + { + "key": "message.compressed_size", + "v_int64": 512, + "v_type": 2 + }, + { + "key": "message.uncompressed_size", + "v_int64": 1024, + "v_type": 2 + } + ] + }, + { + "timestamp": "2017-01-26T21:46:31.639875Z", + "fields": [ + { + "key": "key1", + "v_type": 0, + "v_str": "value1" + } + ] + }, + { + "timestamp": "2017-01-26T21:46:31.639875Z", + "fields": [ + { + "key": "event", + "v_type": 0, + "v_str": "nothing" + }, + { + "key": "description", + "v_type": 0, + "v_str": "annotation description" + } + ] + } + ], + "process": { + "service_name": "api", + "tags": [ + + { + "key": "hostname", + "v_type": 0, + "v_str": "api246-sjc1" + }, + { + "key": "pid", + "v_type": 2, + "v_int64": 13 + }, + { + "key": "start.time", + "v_type": 0, + "v_str": "2017-01-26T21:46:30.639875Z" + }, + { + "key": "ip", + "v_type": 0, + "v_str": "10.53.69.61" + }, + { + "key": "opencensus.exporterversion", + "v_type": 0, + "v_str": "someVersion" + }, + { + "key": "a.bool", + "v_type": 0, + "v_str": "true" + }, + { + "key": "a.double", + "v_type": 0, + "v_str": "1234.56789" + }, + { + "key": "a.long", + "v_type": 0, + "v_str": "123456789" + }, + { + "key": "a.binary", + "v_type": 0, + "v_str": "AQIDBAMCAQ==" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_02.json b/internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_02.json new file mode 100644 index 00000000000..1c3e2270e89 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/jaegerproto_batch_02.json @@ -0,0 +1,80 @@ +{ + "process": { + "service_name": "api", + "tags": null + }, + "spans": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "parent_span_id": 0, + "operation_name": "get", + "start_time": "2017-01-26T21:46:31.639875Z", + "duration": 22938000, + "logs": null, + "process": { + "service_name": "api", + "tags": null + }, + "tags": [ + { + "key": "peer.service", + "v_type": 0, + "v_str": "AAAAAAAAMDk=" + }, + { + "key": "span.kind", + "v_type": 0, + "v_str": "server" + } + ] + }, + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZk=", + "parent_span_id": 0, + "operation_name": "get", + "process": { + "service_name": "api", + "tags": null + }, + "logs": null, + "references": [ + { + "ref_type": 0, + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=" + }, + { + "ref_type": 1, + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABoxOM=" + } + ], + "start_time": "2017-01-26T21:46:31.639875Z", + "duration": 22938000, + "tags": [ + { + "key": "span.kind", + "v_type": 0, + "v_str": "server" + } + ] + }, + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "parent_span_id": 0, + "operation_name": "get2", + "start_time": "2017-01-26T21:46:32.639875Z", + "duration": 22938000, + "logs": null, + "tags": null, + "process": { + "service_name": "api", + "tags": null + } + } + ] + } + \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_01.json b/internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_01.json new file mode 100644 index 00000000000..cb4e53b4004 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_01.json @@ -0,0 +1,139 @@ +{ + "Node": { + "identifier": { + "host_name": "api246-sjc1" + }, + "library_info": { + "exporter_version": "Jaeger-Python-3.1.0" + }, + "service_info": { + "name": "api" + }, + "attributes": { + "a.binary": "AQIDBAMCAQ==", + "a.bool": "true", + "a.double": "1234.56789", + "a.long": "123456789", + "ip": "10.53.69.61" + } + }, + "Resource": null, + "Spans": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "parent_span_id": "AAAAAABoxOM=", + "name": { + "value": "get" + }, + "kind": 1, + "start_time": { + "seconds": 1485467191, + "nanos": 639875000 + }, + "end_time": { + "seconds": 1485467191, + "nanos": 662813000 + }, + "attributes": { + "attribute_map": { + "http.url": { + "Value": { + "StringValue": { + "value": "http://localhost:15598/client_transactions" + } + } + }, + "peer.ipv4": { + "Value": { + "IntValue": 3224716605 + } + }, + "peer.port": { + "Value": { + "IntValue": 53931 + } + }, + "peer.service": { + "Value": { + "StringValue": { + "value": "rtapi" + } + } + }, + "someBool": { + "Value": { + "BoolValue": true + } + }, + "someDouble": { + "Value": { + "DoubleValue": 129.8 + } + }, + "span.kind": { + "Value": { + "StringValue": { + "value": "server" + } + } + } + } + }, + "time_events": { + "time_event": [ + { + "time": { + "seconds": 1485467191, + "nanos": 639875000 + }, + "Value": { + "Annotation": { + "attributes": { + "attribute_map": { + "key1": { + "Value": { + "StringValue": { + "value": "value1" + } + } + }, + "key2": { + "Value": { + "StringValue": { + "value": "value2" + } + } + } + } + } + } + } + }, + { + "time": { + "seconds": 1485467191, + "nanos": 639875000 + }, + "Value": { + "Annotation": { + "attributes": { + "attribute_map": { + "event": { + "Value": { + "StringValue": { + "value": "nothing" + } + } + } + } + } + } + } + } + ] + } + } + ], + "SourceFormat": "" + } \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_02.json b/internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_02.json new file mode 100644 index 00000000000..8db79571bb1 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/ocproto_batch_02.json @@ -0,0 +1,69 @@ +{ + "Node": { + "identifier": {}, + "library_info": {}, + "service_info": { + "name": "api" + } + }, + "Resource": null, + "Spans": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "parent_span_id": "AAAAAABoxOM=", + "name": { + "value": "get" + }, + "start_time": { + "seconds": 1485467191, + "nanos": 639875000 + }, + "end_time": { + "seconds": 1485467191, + "nanos": 662813000 + }, + "attributes": { + "attribute_map": { + "peer.service": { + "Value": { + "StringValue": { + "value": "AAAAAAAAMDk=" + } + } + } + } + } + }, + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZk=", + "parent_span_id": "AAAAAABoxOM=", + "name": { + "value": "get" + }, + "start_time": { + "seconds": 1485467191, + "nanos": 639875000 + }, + "end_time": { + "seconds": 1485467191, + "nanos": 662813000 + }, + "links": { + "link": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "type": 2 + }, + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABoxOM=" + } + ] + } + } + ], + "SourceFormat": "" + } \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_01.json b/internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_01.json new file mode 100644 index 00000000000..99e36923d1b --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_01.json @@ -0,0 +1,149 @@ +{ + "process": { + "service_name": "api", + "tags": [ + { + "key": "hostname", + "v_str": "api246-sjc1" + }, + { + "key": "pid", + "v_type": 2, + "v_int64": 13 + }, + { + "key": "start.time", + "v_str": "2017-01-26T21:46:30.639875Z" + }, + { + "key": "ip", + "v_str": "10.53.69.61" + }, + { + "key": "opencensus.exporterversion", + "v_str": "someVersion" + }, + { + "key": "a.bool", + "v_str": "true" + }, + { + "key": "a.double", + "v_str": "1234.56789" + }, + { + "key": "a.long", + "v_str": "123456789" + }, + { + "key": "a.binary", + "v_str": "AQIDBAMCAQ==" + }, + { + "key": "resource_key1", + "v_str": "resource_val1" + }, + { + "key": "opencensus.resourcetype", + "v_str": "k8s.io/container" + } + ] + }, + "spans": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "references": [ + { + "trace_id" : "AAAAAAAAAABSlpqJVVcaPw==", + "span_id" : "AAAAAABoxOM=", + "refType" : "CHILD_OF" + } + ], + "operation_name": "get", + "start_time": "2017-01-26T21:46:31.639875Z", + "duration": 22938000, + "tags": [ + { + "key": "http.url", + "v_str": "http://localhost:15598/client_transactions" + }, + { + "key": "span.kind", + "v_str": "client" + }, + { + "key": "peer.port", + "v_type": 2, + "v_int64": 53931 + }, + { + "key": "someBool", + "v_type": 1, + "v_bool": true + }, + { + "key": "someDouble", + "v_type": 3, + "v_float64": 129.8 + }, + { + "key": "peer.service", + "v_str": "rtapi" + }, + { + "key": "peer.ipv4", + "v_type": 2, + "v_int64": 3224716605 + } + ], + "logs": [ + { + "timestamp": "2017-01-26T21:46:31.639874Z", + "fields": [ + { + "key": "oc.timeevent.messageevent.id", + "v_type": 2 + }, + { + "key": "oc.timeevent.messageevent.type", + "v_str": "SENT" + }, + { + "key": "oc.timeevent.messageevent.csize", + "v_int64": 512, + "v_type": 2 + }, + { + "key": "oc.timeevent.messageevent.usize", + "v_int64": 1024, + "v_type": 2 + } + ] + }, + { + "timestamp": "2017-01-26T21:46:31.639875Z", + "fields": [ + { + "key": "key1", + "v_str": "value1" + } + ] + }, + { + "timestamp": "2017-01-26T21:46:31.639875Z", + "fields": [ + { + "key": "event", + "v_str": "nothing" + }, + { + "key": "oc.timeevent.annotation.description", + "v_str": "annotation description" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_02.json b/internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_02.json new file mode 100644 index 00000000000..c25987de440 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/proto_batch_no_binary_tags_02.json @@ -0,0 +1,65 @@ +{ + "process": { + "service_name": "api" + }, + "spans": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=", + "operation_name": "get", + "start_time": "2017-01-26T21:46:31.639875Z", + "duration": 22938000, + "tags": [ + { + "key": "peer.service", + "v_str": "AAAAAAAAMDk=" + }, + { + "key": "span.kind", + "v_str": "server" + } + ] + }, + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZk=", + "operation_name": "get", + "references": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=" + }, + { + "ref_type": 1, + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABoxOM=" + }, + { + "ref_type": 1 + } + ], + "start_time": "2017-01-26T21:46:31.639875Z", + "duration": 22938000, + "tags": [ + { + "key": "span.kind", + "v_str": "server" + } + ] + }, + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZA=", + "operation_name": "get2", + "references": [ + { + "trace_id": "AAAAAAAAAABSlpqJVVcaPw==", + "span_id": "AAAAAABkfZg=" + } + ], + "start_time": "2017-01-26T21:46:32.639875Z", + "duration": 22938000 + } + ] + } + \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_01.json b/internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_01.json new file mode 100644 index 00000000000..1f516ed0cf9 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_01.json @@ -0,0 +1,116 @@ +{ + "process": { + "serviceName": "api", + "tags": [ + { + "key": "hostname", + "vType": "STRING", + "vStr": "api246-sjc1" + }, + { + "key": "ip", + "vType": "STRING", + "vStr": "10.53.69.61" + }, + { + "key": "jaeger.version", + "vType": "STRING", + "vStr": "Python-3.1.0" + }, + { + "key": "a.bool", + "vType": "BOOL", + "vBool": true + }, + { + "key": "a.double", + "vType": "DOUBLE", + "vDouble": 1234.56789 + }, + { + "key": "a.long", + "vType": "LONG", + "vLong": 123456789 + }, + { + "key": "a.binary", + "vType": "BINARY", + "vBinary": [1, 2, 3, 4, 3, 2, 1] + } + ] + }, + "spans": [ + { + "traceIdLow": 5951113872249657919, + "spanId": 6585752, + "parentSpanId": 6866147, + "operationName": "get", + "startTime": 1485467191639875, + "duration": 22938, + "tags": [ + { + "key": "http.url", + "vType": "STRING", + "vStr": "http://localhost:15598/client_transactions" + }, + { + "key": "span.kind", + "vType": "STRING", + "vStr": "server" + }, + { + "key": "peer.port", + "vType": "LONG", + "vLong": 53931 + }, + { + "key": "someBool", + "vType": "BOOL", + "vBool": true + }, + { + "key": "someDouble", + "vType": "DOUBLE", + "vDouble": 129.8 + }, + { + "key": "peer.service", + "vType": "STRING", + "vStr": "rtapi" + }, + { + "key": "peer.ipv4", + "vType": "LONG", + "vLong": 3224716605 + } + ], + "logs": [ + { + "timestamp": 1485467191639875, + "fields": [ + { + "key": "key1", + "vType": "STRING", + "vStr": "value1" + }, + { + "key": "key2", + "vType": "STRING", + "vStr": "value2" + } + ] + }, + { + "timestamp": 1485467191639875, + "fields": [ + { + "key": "event", + "vType": "STRING", + "vStr": "nothing" + } + ] + } + ] + } + ] +} diff --git a/internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_02.json b/internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_02.json new file mode 100644 index 00000000000..8d8ff3be39d --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/testdata/thrift_batch_02.json @@ -0,0 +1,44 @@ +{ + "process": { + "serviceName": "api", + "tags": [] + }, + "spans": [ + { + "traceIdLow": 5951113872249657919, + "spanId": 6585752, + "parentSpanId": 6866147, + "operationName": "get", + "startTime": 1485467191639875, + "duration": 22938, + "tags": [ + { + "key": "peer.service", + "vType": "BINARY", + "vBinary": "AAAAAAAAMDk=" + } + ] + }, + { + "traceIdLow": 5951113872249657919, + "spanId": 6585753, + "parentSpanId": 6866147, + "operationName": "get", + "references": [ + { + "refType": "CHILD_OF", + "traceIdLow": 5951113872249657919, + "spanId": 6585752 + }, + { + "refType": "FOLLOWS_FROM", + "traceIdLow": 5951113872249657919, + "spanId": 6866147 + } + ], + "startTime": 1485467191639875, + "duration": 22938 + } + ] + } + \ No newline at end of file diff --git a/internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto.go b/internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto.go new file mode 100644 index 00000000000..887612daece --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto.go @@ -0,0 +1,446 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "fmt" + + "github.com/jaegertracing/jaeger/model" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +// InternalTracesToJaegerProto translates internal trace data into the Jaeger Proto for GRPC. +// Returns slice of translated Jaeger batches and error if translation failed. +func InternalTracesToJaegerProto(td pdata.Traces) ([]*model.Batch, error) { + resourceSpans := td.ResourceSpans() + + if resourceSpans.Len() == 0 { + return nil, nil + } + + batches := make([]*model.Batch, 0, resourceSpans.Len()) + + for i := 0; i < resourceSpans.Len(); i++ { + rs := resourceSpans.At(i) + batch, err := resourceSpansToJaegerProto(rs) + if err != nil { + return nil, err + } + if batch != nil { + batches = append(batches, batch) + } + } + + return batches, nil +} + +func resourceSpansToJaegerProto(rs pdata.ResourceSpans) (*model.Batch, error) { + resource := rs.Resource() + ilss := rs.InstrumentationLibrarySpans() + + if resource.Attributes().Len() == 0 && ilss.Len() == 0 { + return nil, nil + } + + batch := &model.Batch{ + Process: resourceToJaegerProtoProcess(resource), + } + + if ilss.Len() == 0 { + return batch, nil + } + + // Approximate the number of the spans as the number of the spans in the first + // instrumentation library info. + jSpans := make([]*model.Span, 0, ilss.At(0).Spans().Len()) + + for i := 0; i < ilss.Len(); i++ { + ils := ilss.At(i) + spans := ils.Spans() + for j := 0; j < spans.Len(); j++ { + span := spans.At(j) + jSpan, err := spanToJaegerProto(span, ils.InstrumentationLibrary()) + if err != nil { + return nil, err + } + if jSpan != nil { + jSpans = append(jSpans, jSpan) + } + } + } + + batch.Spans = jSpans + + return batch, nil +} + +func resourceToJaegerProtoProcess(resource pdata.Resource) *model.Process { + process := &model.Process{} + attrs := resource.Attributes() + if attrs.Len() == 0 { + process.ServiceName = tracetranslator.ResourceNoServiceName + return process + } + attrsCount := attrs.Len() + if serviceName, ok := attrs.Get(conventions.AttributeServiceName); ok { + process.ServiceName = serviceName.StringVal() + attrsCount-- + } + if attrsCount == 0 { + return process + } + + tags := make([]model.KeyValue, 0, attrsCount) + process.Tags = appendTagsFromResourceAttributes(tags, attrs) + return process + +} + +func appendTagsFromResourceAttributes(dest []model.KeyValue, attrs pdata.AttributeMap) []model.KeyValue { + if attrs.Len() == 0 { + return dest + } + + attrs.ForEach(func(key string, attr pdata.AttributeValue) { + if key == conventions.AttributeServiceName { + return + } + dest = append(dest, attributeToJaegerProtoTag(key, attr)) + }) + return dest +} + +func appendTagsFromAttributes(dest []model.KeyValue, attrs pdata.AttributeMap) []model.KeyValue { + if attrs.Len() == 0 { + return dest + } + attrs.ForEach(func(key string, attr pdata.AttributeValue) { + dest = append(dest, attributeToJaegerProtoTag(key, attr)) + }) + return dest +} + +func attributeToJaegerProtoTag(key string, attr pdata.AttributeValue) model.KeyValue { + tag := model.KeyValue{Key: key} + switch attr.Type() { + case pdata.AttributeValueSTRING: + // Jaeger-to-Internal maps binary tags to string attributes and encodes them as + // base64 strings. Blindingly attempting to decode base64 seems too much. + tag.VType = model.ValueType_STRING + tag.VStr = attr.StringVal() + case pdata.AttributeValueINT: + tag.VType = model.ValueType_INT64 + tag.VInt64 = attr.IntVal() + case pdata.AttributeValueBOOL: + tag.VType = model.ValueType_BOOL + tag.VBool = attr.BoolVal() + case pdata.AttributeValueDOUBLE: + tag.VType = model.ValueType_FLOAT64 + tag.VFloat64 = attr.DoubleVal() + case pdata.AttributeValueMAP, pdata.AttributeValueARRAY: + tag.VType = model.ValueType_STRING + tag.VStr = tracetranslator.AttributeValueToString(attr, false) + } + return tag +} + +func spanToJaegerProto(span pdata.Span, libraryTags pdata.InstrumentationLibrary) (*model.Span, error) { + traceID, err := traceIDToJaegerProto(span.TraceID()) + if err != nil { + return nil, err + } + + spanID, err := spanIDToJaegerProto(span.SpanID()) + if err != nil { + return nil, err + } + + jReferences, err := makeJaegerProtoReferences(span.Links(), span.ParentSpanID(), traceID) + if err != nil { + return nil, fmt.Errorf("error converting span links to Jaeger references: %w", err) + } + + startTime := pdata.UnixNanoToTime(span.StartTime()) + + return &model.Span{ + TraceID: traceID, + SpanID: spanID, + OperationName: span.Name(), + References: jReferences, + StartTime: startTime, + Duration: pdata.UnixNanoToTime(span.EndTime()).Sub(startTime), + Tags: getJaegerProtoSpanTags(span, libraryTags), + Logs: spanEventsToJaegerProtoLogs(span.Events()), + }, nil +} + +func getJaegerProtoSpanTags(span pdata.Span, instrumentationLibrary pdata.InstrumentationLibrary) []model.KeyValue { + var spanKindTag, statusCodeTag, errorTag, statusMsgTag model.KeyValue + var spanKindTagFound, statusCodeTagFound, errorTagFound, statusMsgTagFound bool + + libraryTags, libraryTagsFound := getTagsFromInstrumentationLibrary(instrumentationLibrary) + + tagsCount := span.Attributes().Len() + len(libraryTags) + + spanKindTag, spanKindTagFound = getTagFromSpanKind(span.Kind()) + if spanKindTagFound { + tagsCount++ + } + status := span.Status() + statusCodeTag, statusCodeTagFound = getTagFromStatusCode(status.Code()) + if statusCodeTagFound { + tagsCount++ + } + + errorTag, errorTagFound = getErrorTagFromStatusCode(status.Code()) + if errorTagFound { + tagsCount++ + } + + statusMsgTag, statusMsgTagFound = getTagFromStatusMsg(status.Message()) + if statusMsgTagFound { + tagsCount++ + } + + traceStateTags, traceStateTagsFound := getTagsFromTraceState(span.TraceState()) + if traceStateTagsFound { + tagsCount += len(traceStateTags) + } + + if tagsCount == 0 { + return nil + } + + tags := make([]model.KeyValue, 0, tagsCount) + if libraryTagsFound { + tags = append(tags, libraryTags...) + } + tags = appendTagsFromAttributes(tags, span.Attributes()) + if spanKindTagFound { + tags = append(tags, spanKindTag) + } + if statusCodeTagFound { + tags = append(tags, statusCodeTag) + } + if errorTagFound { + tags = append(tags, errorTag) + } + if statusMsgTagFound { + tags = append(tags, statusMsgTag) + } + if traceStateTagsFound { + tags = append(tags, traceStateTags...) + } + return tags +} + +func traceIDToJaegerProto(traceID pdata.TraceID) (model.TraceID, error) { + traceIDHigh, traceIDLow := tracetranslator.TraceIDToUInt64Pair(traceID) + if traceIDLow == 0 && traceIDHigh == 0 { + return model.TraceID{}, errZeroTraceID + } + return model.TraceID{ + Low: traceIDLow, + High: traceIDHigh, + }, nil +} + +func spanIDToJaegerProto(spanID pdata.SpanID) (model.SpanID, error) { + uSpanID := tracetranslator.BytesToUInt64SpanID(spanID.Bytes()) + if uSpanID == 0 { + return model.SpanID(0), errZeroSpanID + } + return model.SpanID(uSpanID), nil +} + +// makeJaegerProtoReferences constructs jaeger span references based on parent span ID and span links +func makeJaegerProtoReferences( + links pdata.SpanLinkSlice, + parentSpanID pdata.SpanID, + traceID model.TraceID, +) ([]model.SpanRef, error) { + parentSpanIDSet := parentSpanID.IsValid() + if !parentSpanIDSet && links.Len() == 0 { + return nil, nil + } + + refsCount := links.Len() + if parentSpanIDSet { + refsCount++ + } + + refs := make([]model.SpanRef, 0, refsCount) + + // Put parent span ID at the first place because usually backends look for it + // as the first CHILD_OF item in the model.SpanRef slice. + if parentSpanIDSet { + jParentSpanID, err := spanIDToJaegerProto(parentSpanID) + if err != nil { + return nil, fmt.Errorf("OC incorrect parent span ID: %v", err) + } + + refs = append(refs, model.SpanRef{ + TraceID: traceID, + SpanID: jParentSpanID, + RefType: model.SpanRefType_CHILD_OF, + }) + } + + for i := 0; i < links.Len(); i++ { + link := links.At(i) + traceID, err := traceIDToJaegerProto(link.TraceID()) + if err != nil { + continue // skip invalid link + } + + spanID, err := spanIDToJaegerProto(link.SpanID()) + if err != nil { + continue // skip invalid link + } + + refs = append(refs, model.SpanRef{ + TraceID: traceID, + SpanID: spanID, + + // Since Jaeger RefType is not captured in internal data, + // use SpanRefType_FOLLOWS_FROM by default. + // SpanRefType_CHILD_OF supposed to be set only from parentSpanID. + RefType: model.SpanRefType_FOLLOWS_FROM, + }) + } + + return refs, nil +} + +func spanEventsToJaegerProtoLogs(events pdata.SpanEventSlice) []model.Log { + if events.Len() == 0 { + return nil + } + + logs := make([]model.Log, 0, events.Len()) + for i := 0; i < events.Len(); i++ { + event := events.At(i) + fields := make([]model.KeyValue, 0, event.Attributes().Len()+1) + if event.Name() != "" { + fields = append(fields, model.KeyValue{ + Key: tracetranslator.TagMessage, + VType: model.ValueType_STRING, + VStr: event.Name(), + }) + } + fields = appendTagsFromAttributes(fields, event.Attributes()) + logs = append(logs, model.Log{ + Timestamp: pdata.UnixNanoToTime(event.Timestamp()), + Fields: fields, + }) + } + + return logs +} + +func getTagFromSpanKind(spanKind pdata.SpanKind) (model.KeyValue, bool) { + var tagStr string + switch spanKind { + case pdata.SpanKindCLIENT: + tagStr = string(tracetranslator.OpenTracingSpanKindClient) + case pdata.SpanKindSERVER: + tagStr = string(tracetranslator.OpenTracingSpanKindServer) + case pdata.SpanKindPRODUCER: + tagStr = string(tracetranslator.OpenTracingSpanKindProducer) + case pdata.SpanKindCONSUMER: + tagStr = string(tracetranslator.OpenTracingSpanKindConsumer) + case pdata.SpanKindINTERNAL: + tagStr = string(tracetranslator.OpenTracingSpanKindInternal) + default: + return model.KeyValue{}, false + } + + return model.KeyValue{ + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: tagStr, + }, true +} + +func getTagFromStatusCode(statusCode pdata.StatusCode) (model.KeyValue, bool) { + return model.KeyValue{ + Key: tracetranslator.TagStatusCode, + VInt64: int64(statusCode), + VType: model.ValueType_INT64, + }, true +} + +func getErrorTagFromStatusCode(statusCode pdata.StatusCode) (model.KeyValue, bool) { + if statusCode == pdata.StatusCodeError { + return model.KeyValue{ + Key: tracetranslator.TagError, + VBool: true, + VType: model.ValueType_BOOL, + }, true + } + return model.KeyValue{}, false + +} + +func getTagFromStatusMsg(statusMsg string) (model.KeyValue, bool) { + if statusMsg == "" { + return model.KeyValue{}, false + } + return model.KeyValue{ + Key: tracetranslator.TagStatusMsg, + VStr: statusMsg, + VType: model.ValueType_STRING, + }, true +} + +func getTagsFromTraceState(traceState pdata.TraceState) ([]model.KeyValue, bool) { + keyValues := make([]model.KeyValue, 0) + exists := traceState != pdata.TraceStateEmpty + if exists { + // TODO Bring this inline with solution for jaegertracing/jaeger-client-java #702 once available + kv := model.KeyValue{ + Key: tracetranslator.TagW3CTraceState, + VStr: string(traceState), + VType: model.ValueType_STRING, + } + keyValues = append(keyValues, kv) + } + return keyValues, exists +} + +func getTagsFromInstrumentationLibrary(il pdata.InstrumentationLibrary) ([]model.KeyValue, bool) { + keyValues := make([]model.KeyValue, 0) + if ilName := il.Name(); ilName != "" { + kv := model.KeyValue{ + Key: tracetranslator.TagInstrumentationName, + VStr: ilName, + VType: model.ValueType_STRING, + } + keyValues = append(keyValues, kv) + } + if ilVersion := il.Version(); ilVersion != "" { + kv := model.KeyValue{ + Key: tracetranslator.TagInstrumentationVersion, + VStr: ilVersion, + VType: model.ValueType_STRING, + } + keyValues = append(keyValues, kv) + } + + return keyValues, true +} diff --git a/internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto_test.go b/internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto_test.go new file mode 100644 index 00000000000..7a738b1c982 --- /dev/null +++ b/internal/otel_collector/translator/trace/jaeger/traces_to_jaegerproto_test.go @@ -0,0 +1,371 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jaeger + +import ( + "io" + "math/rand" + "testing" + + "github.com/jaegertracing/jaeger/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestGetTagFromStatusCode(t *testing.T) { + tests := []struct { + name string + code pdata.StatusCode + tag model.KeyValue + }{ + { + name: "ok", + code: pdata.StatusCodeOk, + tag: model.KeyValue{ + Key: tracetranslator.TagStatusCode, + VInt64: int64(pdata.StatusCodeOk), + VType: model.ValueType_INT64, + }, + }, + + { + name: "error", + code: pdata.StatusCodeError, + tag: model.KeyValue{ + Key: tracetranslator.TagStatusCode, + VInt64: int64(pdata.StatusCodeError), + VType: model.ValueType_INT64, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, ok := getTagFromStatusCode(test.code) + assert.True(t, ok) + assert.EqualValues(t, test.tag, got) + }) + } +} + +func TestGetErrorTagFromStatusCode(t *testing.T) { + errTag := model.KeyValue{ + Key: tracetranslator.TagError, + VBool: true, + VType: model.ValueType_BOOL, + } + + _, ok := getErrorTagFromStatusCode(pdata.StatusCodeUnset) + assert.False(t, ok) + + _, ok = getErrorTagFromStatusCode(pdata.StatusCodeOk) + assert.False(t, ok) + + got, ok := getErrorTagFromStatusCode(pdata.StatusCodeError) + assert.True(t, ok) + assert.EqualValues(t, errTag, got) +} + +func TestGetTagFromStatusMsg(t *testing.T) { + got, ok := getTagFromStatusMsg("") + assert.False(t, ok) + + got, ok = getTagFromStatusMsg("test-error") + assert.True(t, ok) + assert.EqualValues(t, model.KeyValue{ + Key: tracetranslator.TagStatusMsg, + VStr: "test-error", + VType: model.ValueType_STRING, + }, got) +} + +func TestGetTagFromSpanKind(t *testing.T) { + tests := []struct { + name string + kind pdata.SpanKind + tag model.KeyValue + ok bool + }{ + { + name: "unspecified", + kind: pdata.SpanKindUNSPECIFIED, + tag: model.KeyValue{}, + ok: false, + }, + + { + name: "client", + kind: pdata.SpanKindCLIENT, + tag: model.KeyValue{ + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindClient), + }, + ok: true, + }, + + { + name: "server", + kind: pdata.SpanKindSERVER, + tag: model.KeyValue{ + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindServer), + }, + ok: true, + }, + + { + name: "producer", + kind: pdata.SpanKindPRODUCER, + tag: model.KeyValue{ + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindProducer), + }, + ok: true, + }, + + { + name: "consumer", + kind: pdata.SpanKindCONSUMER, + tag: model.KeyValue{ + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindConsumer), + }, + ok: true, + }, + + { + name: "internal", + kind: pdata.SpanKindINTERNAL, + tag: model.KeyValue{ + Key: tracetranslator.TagSpanKind, + VType: model.ValueType_STRING, + VStr: string(tracetranslator.OpenTracingSpanKindInternal), + }, + ok: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, ok := getTagFromSpanKind(test.kind) + assert.Equal(t, test.ok, ok) + assert.EqualValues(t, test.tag, got) + }) + } +} + +func TestAttributesToJaegerProtoTags(t *testing.T) { + + attributes := pdata.NewAttributeMap() + attributes.InsertBool("bool-val", true) + attributes.InsertInt("int-val", 123) + attributes.InsertString("string-val", "abc") + attributes.InsertDouble("double-val", 1.23) + attributes.InsertString(conventions.AttributeServiceName, "service-name") + + expected := []model.KeyValue{ + { + Key: "bool-val", + VType: model.ValueType_BOOL, + VBool: true, + }, + { + Key: "int-val", + VType: model.ValueType_INT64, + VInt64: 123, + }, + { + Key: "string-val", + VType: model.ValueType_STRING, + VStr: "abc", + }, + { + Key: "double-val", + VType: model.ValueType_FLOAT64, + VFloat64: 1.23, + }, + { + Key: conventions.AttributeServiceName, + VType: model.ValueType_STRING, + VStr: "service-name", + }, + } + + got := appendTagsFromAttributes(make([]model.KeyValue, 0, len(expected)), attributes) + require.EqualValues(t, expected, got) + + // The last item in expected ("service-name") must be skipped in resource tags translation + got = appendTagsFromResourceAttributes(make([]model.KeyValue, 0, len(expected)-1), attributes) + require.EqualValues(t, expected[:4], got) +} + +func TestInternalTracesToJaegerProto(t *testing.T) { + + tests := []struct { + name string + td pdata.Traces + jb *model.Batch + err error + }{ + { + name: "empty", + td: testdata.GenerateTraceDataEmpty(), + err: nil, + }, + + { + name: "no-spans", + td: generateTraceDataResourceOnly(), + jb: &model.Batch{ + Process: generateProtoProcess(), + }, + err: nil, + }, + + { + name: "no-resource-attrs", + td: generateTraceDataResourceOnlyWithNoAttrs(), + err: nil, + }, + + { + name: "one-span-no-resources", + td: generateTraceDataOneSpanNoResourceWithTraceState(), + jb: &model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpanWithTraceState(), + }, + }, + err: nil, + }, + { + name: "library-info", + td: generateTraceDataWithLibraryInfo(), + jb: &model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpanWithLibraryInfo("io.opentelemetry.test"), + }, + }, + err: nil, + }, + { + name: "two-spans-child-parent", + td: generateTraceDataTwoSpansChildParent(), + jb: &model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpan(), + generateProtoChildSpanWithErrorTags(), + }, + }, + err: nil, + }, + + { + name: "two-spans-with-follower", + td: generateTraceDataTwoSpansWithFollower(), + jb: &model.Batch{ + Process: &model.Process{ + ServiceName: tracetranslator.ResourceNoServiceName, + }, + Spans: []*model.Span{ + generateProtoSpan(), + generateProtoFollowerSpan(), + }, + }, + err: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + jbs, err := InternalTracesToJaegerProto(test.td) + assert.EqualValues(t, test.err, err) + if test.jb == nil { + assert.Len(t, jbs, 0) + } else { + require.Equal(t, 1, len(jbs)) + assert.EqualValues(t, test.jb, jbs[0]) + } + }) + } +} + +func TestInternalTracesToJaegerProtoBatchesAndBack(t *testing.T) { + rscSpans, err := goldendataset.GenerateResourceSpans( + "../../../internal/goldendataset/testdata/generated_pict_pairs_traces.txt", + "../../../internal/goldendataset/testdata/generated_pict_pairs_spans.txt", + io.Reader(rand.New(rand.NewSource(2004)))) + assert.NoError(t, err) + for _, rs := range rscSpans { + orig := make([]*otlptrace.ResourceSpans, 1) + orig[0] = rs + td := pdata.TracesFromOtlp(orig) + protoBatches, err := InternalTracesToJaegerProto(td) + assert.NoError(t, err) + tdFromPB := ProtoBatchesToInternalTraces(protoBatches) + assert.NotNil(t, tdFromPB) + assert.Equal(t, td.SpanCount(), tdFromPB.SpanCount()) + } +} + +// generateProtoChildSpanWithErrorTags generates a jaeger span to be used in +// internal->jaeger translation test. It supposed to be the same as generateProtoChildSpan +// that used in jaeger->internal, but jaeger->internal translation infers status code from http status if +// status.code is not set, so the pipeline jaeger->internal->jaeger adds two more tags as the result in that case. +func generateProtoChildSpanWithErrorTags() *model.Span { + span := generateProtoChildSpan() + span.Tags = append(span.Tags, model.KeyValue{ + Key: tracetranslator.TagStatusCode, + VType: model.ValueType_INT64, + VInt64: int64(pdata.StatusCodeError), + }) + span.Tags = append(span.Tags, model.KeyValue{ + Key: tracetranslator.TagError, + VBool: true, + VType: model.ValueType_BOOL, + }) + return span +} + +func BenchmarkInternalTracesToJaegerProto(b *testing.B) { + td := generateTraceDataTwoSpansChildParent() + resource := generateTraceDataResourceOnly().ResourceSpans().At(0).Resource() + resource.CopyTo(td.ResourceSpans().At(0).Resource()) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + InternalTracesToJaegerProto(td) + } +} diff --git a/internal/otel_collector/translator/trace/opencensus_helper.go b/internal/otel_collector/translator/trace/opencensus_helper.go new file mode 100644 index 00000000000..ac00f8ef662 --- /dev/null +++ b/internal/otel_collector/translator/trace/opencensus_helper.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" +) + +// OCAttributeKeyExist returns true if a key in attribute of an OC Span exists. +// It returns false, if attributes is nil, the map itself is nil or the key wasn't found. +func OCAttributeKeyExist(ocAttributes *tracepb.Span_Attributes, key string) bool { + if ocAttributes == nil || ocAttributes.AttributeMap == nil { + return false + } + _, foundKey := ocAttributes.AttributeMap[key] + return foundKey +} diff --git a/internal/otel_collector/translator/trace/protospan_translation.go b/internal/otel_collector/translator/trace/protospan_translation.go new file mode 100644 index 00000000000..6fe70ef2f85 --- /dev/null +++ b/internal/otel_collector/translator/trace/protospan_translation.go @@ -0,0 +1,307 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + "encoding/json" + "fmt" + "math" + "regexp" + "strconv" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// Some of the keys used to represent OTLP constructs as tags or annotations in other formats. +const ( + AnnotationDescriptionKey = "description" + + MessageEventIDKey = "message.id" + MessageEventTypeKey = "message.type" + MessageEventCompressedSizeKey = "message.compressed_size" + MessageEventUncompressedSizeKey = "message.uncompressed_size" + + TagMessage = "message" + + TagSpanKind = "span.kind" + + TagStatusCode = "status.code" + TagStatusMsg = "status.message" + TagError = "error" + TagHTTPStatusCode = "http.status_code" + TagHTTPStatusMsg = "http.status_message" + TagZipkinCensusCode = "census.status_code" + TagZipkinCensusMsg = "census.status_description" + TagZipkinOpenCensusMsg = "opencensus.status_description" + + TagW3CTraceState = "w3c.tracestate" + TagServiceNameSource = "otlp.service.name.source" + TagInstrumentationName = "otlp.instrumentation.library.name" + TagInstrumentationVersion = "otlp.instrumentation.library.version" +) + +// Constants used for signifying batch-level attribute values where not supplied by OTLP data but required +// by other protocols. +const ( + ResourceNoServiceName = "OTLPResourceNoServiceName" +) + +// OpenTracingSpanKind are possible values for TagSpanKind and match the OpenTracing +// conventions: https://github.com/opentracing/specification/blob/master/semantic_conventions.md +// These values are used for representing span kinds that have no +// equivalents in OpenCensus format. They are stored as values of TagSpanKind +type OpenTracingSpanKind string + +const ( + OpenTracingSpanKindUnspecified OpenTracingSpanKind = "" + OpenTracingSpanKindClient OpenTracingSpanKind = "client" + OpenTracingSpanKindServer OpenTracingSpanKind = "server" + OpenTracingSpanKindConsumer OpenTracingSpanKind = "consumer" + OpenTracingSpanKindProducer OpenTracingSpanKind = "producer" + OpenTracingSpanKindInternal OpenTracingSpanKind = "internal" +) + +const ( + SpanLinkDataFormat = "%s|%s|%s|%s|%d" + SpanEventDataFormat = "%s|%s|%d" +) + +type attrValDescript struct { + regex *regexp.Regexp + attrType pdata.AttributeValueType +} + +var attrValDescriptions = getAttrValDescripts() +var complexAttrValDescriptions = getComplexAttrValDescripts() + +func getAttrValDescripts() []*attrValDescript { + descriptions := make([]*attrValDescript, 0, 5) + descriptions = append(descriptions, constructAttrValDescript("^$", pdata.AttributeValueNULL)) + descriptions = append(descriptions, constructAttrValDescript(`^-?\d+$`, pdata.AttributeValueINT)) + descriptions = append(descriptions, constructAttrValDescript(`^-?\d+\.\d+$`, pdata.AttributeValueDOUBLE)) + descriptions = append(descriptions, constructAttrValDescript(`^(true|false)$`, pdata.AttributeValueBOOL)) + descriptions = append(descriptions, constructAttrValDescript(`^\{"\w+":.+\}$`, pdata.AttributeValueMAP)) + descriptions = append(descriptions, constructAttrValDescript(`^\[.*\]$`, pdata.AttributeValueARRAY)) + return descriptions +} + +func getComplexAttrValDescripts() []*attrValDescript { + descriptions := getAttrValDescripts() + return descriptions[4:] +} + +func constructAttrValDescript(regex string, attrType pdata.AttributeValueType) *attrValDescript { + regexc := regexp.MustCompile(regex) + return &attrValDescript{ + regex: regexc, + attrType: attrType, + } +} + +// AttributeValueToString converts an OTLP AttributeValue object to its equivalent string representation +func AttributeValueToString(attr pdata.AttributeValue, jsonLike bool) string { + switch attr.Type() { + case pdata.AttributeValueNULL: + if jsonLike { + return "null" + } + return "" + case pdata.AttributeValueSTRING: + if jsonLike { + return fmt.Sprintf("%q", attr.StringVal()) + } + return attr.StringVal() + + case pdata.AttributeValueBOOL: + return strconv.FormatBool(attr.BoolVal()) + + case pdata.AttributeValueDOUBLE: + return strconv.FormatFloat(attr.DoubleVal(), 'f', -1, 64) + + case pdata.AttributeValueINT: + return strconv.FormatInt(attr.IntVal(), 10) + + case pdata.AttributeValueMAP: + jsonStr, _ := json.Marshal(AttributeMapToMap(attr.MapVal())) + return string(jsonStr) + + case pdata.AttributeValueARRAY: + jsonStr, _ := json.Marshal(AttributeArrayToSlice(attr.ArrayVal())) + return string(jsonStr) + + default: + return fmt.Sprintf("", attr.Type()) + } +} + +// AttributeMapToMap converts an OTLP AttributeMap to a standard go map +func AttributeMapToMap(attrMap pdata.AttributeMap) map[string]interface{} { + rawMap := make(map[string]interface{}) + attrMap.ForEach(func(k string, v pdata.AttributeValue) { + switch v.Type() { + case pdata.AttributeValueSTRING: + rawMap[k] = v.StringVal() + case pdata.AttributeValueINT: + rawMap[k] = v.IntVal() + case pdata.AttributeValueDOUBLE: + rawMap[k] = v.DoubleVal() + case pdata.AttributeValueBOOL: + rawMap[k] = v.BoolVal() + case pdata.AttributeValueNULL: + rawMap[k] = nil + case pdata.AttributeValueMAP: + rawMap[k] = AttributeMapToMap(v.MapVal()) + case pdata.AttributeValueARRAY: + rawMap[k] = AttributeArrayToSlice(v.ArrayVal()) + } + }) + return rawMap +} + +func AttributeArrayToSlice(attrArray pdata.AnyValueArray) []interface{} { + rawSlice := make([]interface{}, 0, attrArray.Len()) + for i := 0; i < attrArray.Len(); i++ { + v := attrArray.At(i) + switch v.Type() { + case pdata.AttributeValueSTRING: + rawSlice = append(rawSlice, v.StringVal()) + case pdata.AttributeValueINT: + rawSlice = append(rawSlice, v.IntVal()) + case pdata.AttributeValueDOUBLE: + rawSlice = append(rawSlice, v.DoubleVal()) + case pdata.AttributeValueBOOL: + rawSlice = append(rawSlice, v.BoolVal()) + case pdata.AttributeValueNULL: + rawSlice = append(rawSlice, nil) + default: + rawSlice = append(rawSlice, "") + } + } + return rawSlice +} + +// UpsertStringToAttributeMap upserts a string value to the specified key as it's native OTLP type +func UpsertStringToAttributeMap(key string, val string, dest pdata.AttributeMap, omitSimpleTypes bool) { + switch DetermineValueType(val, omitSimpleTypes) { + case pdata.AttributeValueINT: + iVal, _ := strconv.ParseInt(val, 10, 64) + dest.UpsertInt(key, iVal) + case pdata.AttributeValueDOUBLE: + fVal, _ := strconv.ParseFloat(val, 64) + dest.UpsertDouble(key, fVal) + case pdata.AttributeValueBOOL: + bVal, _ := strconv.ParseBool(val) + dest.UpsertBool(key, bVal) + case pdata.AttributeValueMAP: + var attrs map[string]interface{} + err := json.Unmarshal([]byte(val), &attrs) + if err == nil { + attrMap := pdata.NewAttributeValueMap() + jsonMapToAttributeMap(attrs, attrMap.MapVal()) + dest.Upsert(key, attrMap) + } else { + dest.UpsertString(key, "") + } + case pdata.AttributeValueARRAY: + var jArray []interface{} + err := json.Unmarshal([]byte(val), &jArray) + if err == nil { + attrArr := pdata.NewAttributeValueArray() + jsonArrayToAttributeArray(jArray, attrArr.ArrayVal()) + dest.Upsert(key, attrArr) + } else { + dest.UpsertString(key, "") + } + default: + dest.UpsertString(key, val) + } +} + +// DetermineValueType returns the native OTLP attribute type the string translates to. +func DetermineValueType(value string, omitSimpleTypes bool) pdata.AttributeValueType { + if omitSimpleTypes { + for _, desc := range complexAttrValDescriptions { + if desc.regex.MatchString(value) { + return desc.attrType + } + } + } else { + for _, desc := range attrValDescriptions { + if desc.regex.MatchString(value) { + return desc.attrType + } + } + } + return pdata.AttributeValueSTRING +} + +func jsonMapToAttributeMap(attrs map[string]interface{}, dest pdata.AttributeMap) { + for key, val := range attrs { + if val == nil { + dest.Upsert(key, pdata.NewAttributeValueNull()) + continue + } + if s, ok := val.(string); ok { + dest.UpsertString(key, s) + } else if d, ok := val.(float64); ok { + if math.Mod(d, 1.0) == 0.0 { + dest.UpsertInt(key, int64(d)) + } else { + dest.UpsertDouble(key, d) + } + } else if b, ok := val.(bool); ok { + dest.UpsertBool(key, b) + } else if m, ok := val.(map[string]interface{}); ok { + value := pdata.NewAttributeValueMap() + jsonMapToAttributeMap(m, value.MapVal()) + dest.Upsert(key, value) + } else if a, ok := val.([]interface{}); ok { + value := pdata.NewAttributeValueArray() + jsonArrayToAttributeArray(a, value.ArrayVal()) + dest.Upsert(key, value) + } + } +} + +func jsonArrayToAttributeArray(jArray []interface{}, dest pdata.AnyValueArray) { + for _, val := range jArray { + if val == nil { + dest.Append(pdata.NewAttributeValueNull()) + continue + } + if s, ok := val.(string); ok { + dest.Append(pdata.NewAttributeValueString(s)) + } else if d, ok := val.(float64); ok { + if math.Mod(d, 1.0) == 0.0 { + dest.Append(pdata.NewAttributeValueInt(int64(d))) + } else { + dest.Append(pdata.NewAttributeValueDouble(d)) + } + } else if b, ok := val.(bool); ok { + dest.Append(pdata.NewAttributeValueBool(b)) + } else { + dest.Append(pdata.NewAttributeValueString("")) + } + } +} + +// StatusCodeFromHTTP takes an HTTP status code and return the appropriate OpenTelemetry status code +// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#status +func StatusCodeFromHTTP(httpStatusCode int) pdata.StatusCode { + if httpStatusCode >= 100 && httpStatusCode < 399 { + return pdata.StatusCodeUnset + } + return pdata.StatusCodeError +} diff --git a/internal/otel_collector/translator/trace/protospan_translation_test.go b/internal/otel_collector/translator/trace/protospan_translation_test.go new file mode 100644 index 00000000000..b70e455fafe --- /dev/null +++ b/internal/otel_collector/translator/trace/protospan_translation_test.go @@ -0,0 +1,190 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetranslator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestAttributeValueToString(t *testing.T) { + tests := []struct { + name string + input pdata.AttributeValue + jsonLike bool + expected string + }{ + { + name: "string", + input: pdata.NewAttributeValueString("string value"), + jsonLike: false, + expected: "string value", + }, + { + name: "json string", + input: pdata.NewAttributeValueString("string value"), + jsonLike: true, + expected: "\"string value\"", + }, + { + name: "int64", + input: pdata.NewAttributeValueInt(42), + jsonLike: false, + expected: "42", + }, + { + name: "float64", + input: pdata.NewAttributeValueDouble(1.61803399), + jsonLike: false, + expected: "1.61803399", + }, + { + name: "boolean", + input: pdata.NewAttributeValueBool(true), + jsonLike: false, + expected: "true", + }, + { + name: "null", + input: pdata.NewAttributeValueNull(), + jsonLike: false, + expected: "", + }, + { + name: "null", + input: pdata.NewAttributeValueNull(), + jsonLike: true, + expected: "null", + }, + { + name: "map", + input: pdata.NewAttributeValueMap(), + jsonLike: false, + expected: "{}", + }, + { + name: "array", + input: pdata.NewAttributeValueArray(), + jsonLike: false, + expected: "[]", + }, + { + name: "array", + input: pdata.NewAttributeValueNull(), + jsonLike: false, + expected: "", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := AttributeValueToString(test.input, test.jsonLike) + assert.Equal(t, test.expected, actual) + dest := pdata.NewAttributeMap() + key := "keyOne" + UpsertStringToAttributeMap(key, actual, dest, false) + val, ok := dest.Get(key) + assert.True(t, ok) + if !test.jsonLike { + switch test.input.Type() { + case pdata.AttributeValueINT, pdata.AttributeValueDOUBLE, pdata.AttributeValueBOOL: + assert.EqualValues(t, test.input, val) + case pdata.AttributeValueARRAY: + assert.NotNil(t, val) + default: + assert.Equal(t, test.expected, val.StringVal()) + } + } + }) + } +} + +func TestAttributeMapToStringAndBack(t *testing.T) { + expected := pdata.NewAttributeValueMap() + attrMap := expected.MapVal() + attrMap.UpsertString("strKey", "strVal") + attrMap.UpsertInt("intKey", 7) + attrMap.UpsertDouble("floatKey", 18.6) + attrMap.UpsertBool("boolKey", false) + attrMap.Upsert("nullKey", pdata.NewAttributeValueNull()) + attrMap.Upsert("mapKey", constructTestAttributeSubmap()) + attrMap.Upsert("arrKey", constructTestAttributeSubarray()) + strVal := AttributeValueToString(expected, false) + dest := pdata.NewAttributeMap() + UpsertStringToAttributeMap("parent", strVal, dest, false) + actual, ok := dest.Get("parent") + assert.True(t, ok) + compareMaps(t, attrMap, actual.MapVal()) +} + +func TestAttributeArrayToStringAndBack(t *testing.T) { + expected := pdata.NewAttributeValueArray() + attrArr := expected.ArrayVal() + attrArr.Append(pdata.NewAttributeValueString("strVal")) + attrArr.Append(pdata.NewAttributeValueInt(7)) + attrArr.Append(pdata.NewAttributeValueDouble(18.6)) + attrArr.Append(pdata.NewAttributeValueBool(false)) + attrArr.Append(pdata.NewAttributeValueNull()) + strVal := AttributeValueToString(expected, false) + dest := pdata.NewAttributeMap() + UpsertStringToAttributeMap("parent", strVal, dest, false) + actual, ok := dest.Get("parent") + assert.True(t, ok) + compareArrays(t, attrArr, actual.ArrayVal()) +} + +func compareMaps(t *testing.T, expected pdata.AttributeMap, actual pdata.AttributeMap) { + expected.ForEach(func(k string, e pdata.AttributeValue) { + a, ok := actual.Get(k) + assert.True(t, ok) + if ok { + if e.Type() == pdata.AttributeValueMAP { + compareMaps(t, e.MapVal(), a.MapVal()) + } else { + assert.Equal(t, e, a) + } + } + }) +} + +func compareArrays(t *testing.T, expected pdata.AnyValueArray, actual pdata.AnyValueArray) { + for i := 0; i < expected.Len(); i++ { + e := expected.At(i) + a := actual.At(i) + if e.Type() == pdata.AttributeValueMAP { + compareMaps(t, e.MapVal(), a.MapVal()) + } else { + assert.Equal(t, e, a) + } + } +} + +func constructTestAttributeSubmap() pdata.AttributeValue { + value := pdata.NewAttributeValueMap() + value.MapVal().UpsertString("keyOne", "valOne") + value.MapVal().UpsertString("keyTwo", "valTwo") + return value +} + +func constructTestAttributeSubarray() pdata.AttributeValue { + value := pdata.NewAttributeValueArray() + a1 := pdata.NewAttributeValueString("strOne") + value.ArrayVal().Append(a1) + a2 := pdata.NewAttributeValueString("strTwo") + value.ArrayVal().Append(a2) + return value +} diff --git a/internal/otel_collector/translator/trace/zipkin/attributekeys.go b/internal/otel_collector/translator/trace/zipkin/attributekeys.go new file mode 100644 index 00000000000..cf062a3eb8d --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/attributekeys.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +// These constants are the attribute keys used when translating from zipkin +// format to the internal collector data format. +const ( + LocalEndpointIPv4 = "ipv4" + LocalEndpointIPv6 = "ipv6" + LocalEndpointPort = "port" + LocalEndpointServiceName = "serviceName" + RemoteEndpointIPv4 = "zipkin.remoteEndpoint.ipv4" + RemoteEndpointIPv6 = "zipkin.remoteEndpoint.ipv6" + RemoteEndpointPort = "zipkin.remoteEndpoint.port" + RemoteEndpointServiceName = "zipkin.remoteEndpoint.serviceName" + StartTimeAbsent = "otel.zipkin.absentField.startTime" +) diff --git a/internal/otel_collector/translator/trace/zipkin/status_code.go b/internal/otel_collector/translator/trace/zipkin/status_code.go new file mode 100644 index 00000000000..c453749e918 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/status_code.go @@ -0,0 +1,197 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "fmt" + "math" + "strconv" + + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +type status struct { + codePtr *int32 + message string +} + +// statusMapper contains codes translated from different sources to OC status codes +type statusMapper struct { + // oc status code extracted from "status.code" tags + fromStatus status + // oc status code extracted from "census.status_code" tags + fromCensus status + // oc status code extracted from "http.status_code" tags + fromHTTP status + // oc status code extracted from "error" tags + fromErrorTag status + // oc status code 'unknown' when the "error" tag exists but is invalid + fromErrorTagUnknown status +} + +// ocStatus returns an OC status from the best possible extraction source. +// It'll first try to return status extracted from "census.status_code" to account for zipkin +// then fallback on code extracted from "status.code" tags +// and finally fallback on code extracted and translated from "http.status_code" +// ocStatus must be called after all tags/attributes are processed with the `fromAttribute` method. +func (m *statusMapper) ocStatus() *tracepb.Status { + var s status + switch { + case m.fromCensus.codePtr != nil: + s = m.fromCensus + case m.fromStatus.codePtr != nil: + s = m.fromStatus + case m.fromErrorTag.codePtr != nil: + s = m.fromErrorTag + if m.fromCensus.message != "" { + s.message = m.fromCensus.message + } else if m.fromStatus.message != "" { + s.message = m.fromStatus.message + } + case m.fromHTTP.codePtr != nil: + s = m.fromHTTP + default: + s = m.fromErrorTagUnknown + } + + if s.codePtr != nil { + code := int32(0) + if s.codePtr != nil { + code = *s.codePtr + } + return &tracepb.Status{ + Code: code, + Message: s.message, + } + } + return nil +} + +func (m *statusMapper) fromAttribute(key string, attrib *tracepb.AttributeValue) bool { + switch key { + case tracetranslator.TagZipkinCensusCode: + code, err := attribToStatusCode(attrib) + if err == nil { + m.fromCensus.codePtr = &code + } + return true + + case tracetranslator.TagZipkinCensusMsg, tracetranslator.TagZipkinOpenCensusMsg: + m.fromCensus.message = attrib.GetStringValue().GetValue() + return true + + case tracetranslator.TagStatusCode: + code, err := attribToStatusCode(attrib) + if err == nil { + m.fromStatus.codePtr = &code + } + return true + + case tracetranslator.TagStatusMsg: + m.fromStatus.message = attrib.GetStringValue().GetValue() + return true + + case tracetranslator.TagHTTPStatusCode: + httpCode, err := attribToStatusCode(attrib) + if err == nil { + code := tracetranslator.OCStatusCodeFromHTTP(httpCode) + m.fromHTTP.codePtr = &code + } + + case tracetranslator.TagHTTPStatusMsg: + m.fromHTTP.message = attrib.GetStringValue().GetValue() + + case tracetranslator.TagError: + code, ok := extractStatusFromError(attrib) + if ok { + m.fromErrorTag.codePtr = code + return true + } + m.fromErrorTagUnknown.codePtr = code + } + return false +} + +// attribToStatusCode maps an integer or string attribute value to a status code. +// The function return nil if the value is of another type or cannot be converted to an int32 value. +func attribToStatusCode(attr *tracepb.AttributeValue) (int32, error) { + if attr == nil { + return 0, fmt.Errorf("nil attribute") + } + + switch val := attr.Value.(type) { + case *tracepb.AttributeValue_IntValue: + return toInt32(int(val.IntValue)) + case *tracepb.AttributeValue_StringValue: + i, err := strconv.Atoi(val.StringValue.GetValue()) + if err != nil { + return 0, err + } + return toInt32(i) + } + return 0, fmt.Errorf("invalid attribute type") +} + +func toInt32(i int) (int32, error) { + if i <= math.MaxInt32 && i >= math.MinInt32 { + return int32(i), nil + } + return 0, fmt.Errorf("outside of the int32 range") +} + +func extractStatusFromError(attrib *tracepb.AttributeValue) (*int32, bool) { + // The status is stored with the "error" key + // See https://github.com/census-instrumentation/opencensus-go/blob/1eb9a13c7dd02141e065a665f6bf5c99a090a16a/exporter/zipkin/zipkin.go#L160-L165 + var unknown int32 = 2 + + switch val := attrib.Value.(type) { + case *tracepb.AttributeValue_StringValue: + canonicalCodeStr := val.StringValue.GetValue() + if canonicalCodeStr == "" { + return nil, true + } + code, set := canonicalCodesMap[canonicalCodeStr] + if set { + return &code, true + } + default: + break + } + + return &unknown, false +} + +var canonicalCodesMap = map[string]int32{ + // https://github.com/googleapis/googleapis/blob/bee79fbe03254a35db125dc6d2f1e9b752b390fe/google/rpc/code.proto#L33-L186 + "OK": 0, + "CANCELLED": 1, + "UNKNOWN": 2, + "INVALID_ARGUMENT": 3, + "DEADLINE_EXCEEDED": 4, + "NOT_FOUND": 5, + "ALREADY_EXISTS": 6, + "PERMISSION_DENIED": 7, + "RESOURCE_EXHAUSTED": 8, + "FAILED_PRECONDITION": 9, + "ABORTED": 10, + "OUT_OF_RANGE": 11, + "UNIMPLEMENTED": 12, + "INTERNAL": 13, + "UNAVAILABLE": 14, + "DATA_LOSS": 15, + "UNAUTHENTICATED": 16, +} diff --git a/internal/otel_collector/translator/trace/zipkin/status_code_test.go b/internal/otel_collector/translator/trace/zipkin/status_code_test.go new file mode 100644 index 00000000000..c568dd34b1d --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/status_code_test.go @@ -0,0 +1,277 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "fmt" + "strconv" + "testing" + + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/stretchr/testify/assert" +) + +func TestAttribToStatusCode(t *testing.T) { + _, atoiError := strconv.Atoi("nan") + + tests := []struct { + name string + attr *tracepb.AttributeValue + code int32 + err error + }{ + { + name: "nil", + attr: nil, + code: 0, + err: fmt.Errorf("nil attribute"), + }, + + { + name: "valid-int-code", + attr: &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_IntValue{IntValue: int64(0)}, + }, + code: 0, + err: nil, + }, + + { + name: "invalid-int-code", + attr: &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_IntValue{IntValue: int64(1 << 32)}, + }, + code: 0, + err: fmt.Errorf("outside of the int32 range"), + }, + + { + name: "valid-string-code", + attr: &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_StringValue{StringValue: &tracepb.TruncatableString{Value: "200"}}, + }, + code: 200, + err: nil, + }, + + { + name: "invalid-string-code", + attr: &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_StringValue{StringValue: &tracepb.TruncatableString{Value: "nan"}}, + }, + code: 0, + err: atoiError, + }, + + { + name: "bool-code", + attr: &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_BoolValue{BoolValue: true}, + }, + code: 0, + err: fmt.Errorf("invalid attribute type"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := attribToStatusCode(test.attr) + assert.Equal(t, test.code, got) + assert.Equal(t, test.err, err) + }) + } +} + +func TestStatusCodeMapperCases(t *testing.T) { + tests := []struct { + name string + expected *tracepb.Status + attributes map[string]string + }{ + { + name: "no relevant attributes", + expected: nil, + attributes: map[string]string{ + "not.relevant": "data", + }, + }, + + { + name: "http: 500", + expected: &tracepb.Status{Code: 13}, + attributes: map[string]string{ + "http.status_code": "500", + }, + }, + + { + name: "http: message only, nil", + expected: nil, + attributes: map[string]string{ + "http.status_message": "something", + }, + }, + + { + name: "http: 500", + expected: &tracepb.Status{Code: 13, Message: "a message"}, + attributes: map[string]string{ + "http.status_code": "500", + "http.status_message": "a message", + }, + }, + + { + name: "http: 500, with error attribute", + expected: &tracepb.Status{Code: 13}, + attributes: map[string]string{ + "http.status_code": "500", + "error": "an error occurred", + }, + }, + + { + name: "oc: internal", + expected: &tracepb.Status{Code: 13, Message: "a description"}, + attributes: map[string]string{ + "census.status_code": "13", + "census.status_description": "a description", + }, + }, + + { + name: "oc: description and error", + expected: &tracepb.Status{Code: 13, Message: "a description"}, + attributes: map[string]string{ + "opencensus.status_description": "a description", + "error": "INTERNAL", + }, + }, + + { + name: "oc: error only", + expected: &tracepb.Status{Code: 13, Message: ""}, + attributes: map[string]string{ + "error": "INTERNAL", + }, + }, + + { + name: "oc: empty error tag", + expected: nil, + attributes: map[string]string{ + "error": "", + }, + }, + + { + name: "oc: description only, no status", + expected: nil, + attributes: map[string]string{ + "opencensus.status_description": "a description", + }, + }, + + { + name: "oc: priority over http", + expected: &tracepb.Status{Code: 4, Message: "deadline expired"}, + attributes: map[string]string{ + "census.status_description": "deadline expired", + "census.status_code": "4", + + "http.status_message": "a description", + "http.status_code": "500", + }, + }, + + { + name: "error: valid oc status priority over http", + expected: &tracepb.Status{Code: 4}, + attributes: map[string]string{ + "error": "DEADLINE_EXCEEDED", + + "http.status_message": "a description", + "http.status_code": "500", + }, + }, + + { + name: "error: invalid oc status uses http", + expected: &tracepb.Status{Code: 13, Message: "a description"}, + attributes: map[string]string{ + "error": "123", + + "http.status_message": "a description", + "http.status_code": "500", + }, + }, + + { + name: "error only: string description", + expected: &tracepb.Status{Code: 2}, + attributes: map[string]string{ + "error": "a description", + }, + }, + + { + name: "error only: true", + expected: &tracepb.Status{Code: 2}, + attributes: map[string]string{ + "error": "true", + }, + }, + + { + name: "error only: false", + expected: &tracepb.Status{Code: 2}, + attributes: map[string]string{ + "error": "false", + }, + }, + + { + name: "error only: 1", + expected: &tracepb.Status{Code: 2}, + attributes: map[string]string{ + "error": "1", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + attributes := attributesFromMap(test.attributes) + + sMapper := &statusMapper{} + for k, v := range attributes { + sMapper.fromAttribute(k, v) + } + + got := sMapper.ocStatus() + assert.EqualValues(t, test.expected, got) + }) + } +} + +func attributesFromMap(mapValues map[string]string) map[string]*tracepb.AttributeValue { + res := map[string]*tracepb.AttributeValue{} + + for k, v := range mapValues { + pbAttrib := parseAnnotationValue(v, false) + res[k] = pbAttrib + } + return res +} diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_error_batch.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_error_batch.json new file mode 100644 index 00000000000..372289ee72a --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_error_batch.json @@ -0,0 +1,64 @@ +[ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkAvailability", + "id": "0ed2e63cbe71f5a8", + "annotations": [ + { + "timestamp": 1544805927448081, + "value": "sr", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927450000, + "value": "custom time event", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927460102, + "value": "ss", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + } + ] + }, + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "f9ebb6e64880612a", + "parentId": "BADID", + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [ + { + "timestamp": 1544805927453923, + "value": "cs", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927457717, + "value": "cr", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + } + ] + } +] diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_local_component.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_local_component.json new file mode 100644 index 00000000000..10114470553 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_local_component.json @@ -0,0 +1,37 @@ +[ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "fe351a053fbcac1f", + "parentId": "0ed2e63cbe71f5a8", + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [], + "binaryAnnotations": [ + { + "key": "lc", + "value": "myLocalComponent" + } + ] + }, + { + "traceId": "0ed2e63cbe71f5a8", + "name": "sendOrder", + "id": "fe351a053fbcac2f", + "parentId": "0ed2e63cbe71f5a8", + "timestamp": 1544805927453925, + "duration": 3740, + "annotations": [], + "binaryAnnotations": [ + { + "key": "lc", + "value": "myLocalComponent", + "endpoint": { + "ipv4": "172.31.0.7", + "port": 0, + "serviceName": "myServiceName" + } + } + ] + } +] diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_multiple_batches.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_multiple_batches.json new file mode 100644 index 00000000000..8e1b58f2032 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_multiple_batches.json @@ -0,0 +1,154 @@ +[ + [ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkAvailability", + "id": "0ed2e63cbe71f5a8", + "annotations": [ + { + "timestamp": 1544805927448081, + "value": "sr", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927450000, + "value": "custom time event", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927460102, + "value": "ss", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + } + ] + } + ], + [ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "f9ebb6e64880612a", + "parentId": "0ed2e63cbe71f5a8", + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [ + { + "timestamp": 1544805927453923, + "value": "cs", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927457717, + "value": "cr", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + } + ] + } + ], + [ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkAvailability", + "id": "0ed2e63cbe71f5a8", + "timestamp": 1544805927446743, + "duration": 12956, + "annotations": [ + { + "timestamp": 1544805927446743, + "value": "cs", + "endpoint": { + "ipv4": "172.31.0.2", + "port": 0, + "serviceName": "front-proxy" + } + }, + { + "timestamp": 1544805927460510, + "value": "cr", + "endpoint": { + "ipv4": "172.31.0.2", + "port": 0, + "serviceName": "front-proxy" + } + } + ] + } + ], + [ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "f9ebb6e64880612a", + "parentId": "0ed2e63cbe71f5a8", + "annotations": [ + { + "timestamp": 1544805927454487, + "value": "sr", + "endpoint": { + "ipv4": "172.31.0.7", + "port": 0, + "serviceName": "service2" + } + }, + { + "timestamp": 1544805927457320, + "value": "ss", + "endpoint": { + "ipv4": "172.31.0.7", + "port": 0, + "serviceName": "service2" + } + } + ], + "binaryAnnotations": [ + { + "key": "http.url", + "value": "http://localhost:9000/trace/2" + }, + { + "key": "http.status_code", + "value": "200" + }, + { + "key": "success", + "value": "true" + }, + { + "key": "processed", + "value": "1.5" + } + ] + } + ], + [ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "fe351a053fbcac1f", + "parentId": "0ed2e63cbe71f5a8", + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [] + } + ] +] diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_single_batch.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_single_batch.json new file mode 100644 index 00000000000..c446e320952 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_single_batch.json @@ -0,0 +1,144 @@ +[ + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkAvailability", + "id": "0ed2e63cbe71f5a8", + "annotations": [ + { + "timestamp": 1544805927448081, + "value": "sr", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927450000, + "value": "custom time event", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927460102, + "value": "ss", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + } + ] + }, + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "f9ebb6e64880612a", + "parentId": "0ed2e63cbe71f5a8", + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [ + { + "timestamp": 1544805927453923, + "value": "cs", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + }, + { + "timestamp": 1544805927457717, + "value": "cr", + "endpoint": { + "ipv4": "172.31.0.4", + "port": 0, + "serviceName": "service1" + } + } + ] + }, + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkAvailability", + "id": "0ed2e63cbe71f5a8", + "timestamp": 1544805927446743, + "duration": 12956, + "annotations": [ + { + "timestamp": 1544805927446743, + "value": "cs", + "endpoint": { + "ipv4": "172.31.0.2", + "port": 0, + "serviceName": "front-proxy" + } + }, + { + "timestamp": 1544805927460510, + "value": "cr", + "endpoint": { + "ipv4": "172.31.0.2", + "port": 0, + "serviceName": "front-proxy" + } + } + ] + }, + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "f9ebb6e64880612a", + "parentId": "0ed2e63cbe71f5a8", + "annotations": [ + { + "timestamp": 1544805927454487, + "value": "sr", + "endpoint": { + "ipv4": "172.31.0.7", + "port": 0, + "serviceName": "service2" + } + }, + { + "timestamp": 1544805927457320, + "value": "ss", + "endpoint": { + "ipv4": "172.31.0.7", + "port": 0, + "serviceName": "service2" + } + } + ], + "binaryAnnotations": [ + { + "key": "http.url", + "value": "http://localhost:9000/trace/2" + }, + { + "key": "http.status_code", + "value": "200" + }, + { + "key": "success", + "value": "true" + }, + { + "key": "processed", + "value": "1.5" + } + ] + }, + { + "traceId": "0ed2e63cbe71f5a8", + "name": "checkStock", + "id": "fe351a053fbcac1f", + "parentId": "0ed2e63cbe71f5a8", + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [] + } +] diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_local_component.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_local_component.json new file mode 100644 index 00000000000..d6f099dbe43 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_local_component.json @@ -0,0 +1,39 @@ +[ + { + "trace_id": 1068169210207794600, + "name": "checkStock", + "id": -129168404463703009, + "parent_id": 1068169210207794600, + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [], + "binary_annotations": [ + { + "key": "lc", + "annotation_type": "STRING", + "value": "bXlMb2NhbENvbXBvbmVudA==" + } + ] + }, + { + "trace_id": 1068169210207794600, + "name": "sendOrder", + "id": -129168404463703007, + "parent_id": 1068169210207794600, + "timestamp": 1544805927453925, + "duration": 3740, + "annotations": [], + "binary_annotations": [ + { + "key": "lc", + "annotation_type": "STRING", + "value": "bXlMb2NhbENvbXBvbmVudA==", + "host": { + "ipv4": -1407254521, + "port": 0, + "service_name": "myServiceName" + } + } + ] + } +] diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_single_batch.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_single_batch.json new file mode 100644 index 00000000000..6bbd0c8778c --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v1_thrift_single_batch.json @@ -0,0 +1,148 @@ +[ + { + "trace_id": 1068169210207794600, + "name": "checkAvailability", + "id": 1068169210207794600, + "annotations": [ + { + "timestamp": 1544805927448081, + "value": "sr", + "host": { + "ipv4": -1407254524, + "port": 0, + "service_name": "service1" + } + }, + { + "timestamp": 1544805927450000, + "value": "custom time event", + "host": { + "ipv4": -1407254524, + "port": 0, + "service_name": "service1" + } + }, + { + "timestamp": 1544805927460102, + "value": "ss", + "host": { + "ipv4": -1407254524, + "port": 0, + "service_name": "service1" + } + } + ] + }, + { + "trace_id": 1068169210207794600, + "name": "checkStock", + "id": -438055438563385046, + "parent_id": 1068169210207794600, + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [ + { + "timestamp": 1544805927453923, + "value": "cs", + "host": { + "ipv4": -1407254524, + "port": 0, + "service_name": "service1" + } + }, + { + "timestamp": 1544805927457717, + "value": "cr", + "host": { + "ipv4": -1407254524, + "port": 0, + "service_name": "service1" + } + } + ] + }, + { + "trace_id": 1068169210207794600, + "name": "checkAvailability", + "id": 1068169210207794600, + "timestamp": 1544805927446743, + "duration": 12956, + "annotations": [ + { + "timestamp": 1544805927446743, + "value": "cs", + "host": { + "ipv4": -1407254526, + "port": 0, + "service_name": "front-proxy" + } + }, + { + "timestamp": 1544805927460510, + "value": "cr", + "host": { + "ipv4": -1407254526, + "port": 0, + "service_name": "front-proxy" + } + } + ] + }, + { + "trace_id": 1068169210207794600, + "name": "checkStock", + "id": -438055438563385046, + "parent_id": 1068169210207794600, + "annotations": [ + { + "timestamp": 1544805927454487, + "value": "sr", + "host": { + "ipv4": -1407254521, + "port": 0, + "service_name": "service2" + } + }, + { + "timestamp": 1544805927457320, + "value": "ss", + "host": { + "ipv4": -1407254521, + "port": 0, + "service_name": "service2" + } + } + ], + "binary_annotations": [ + { + "key": "http.url", + "annotation_type": "STRING", + "value": "aHR0cDovL2xvY2FsaG9zdDo5MDAwL3RyYWNlLzI=" + }, + { + "key": "http.status_code", + "annotation_type": "I64", + "value": "AAAAAAAAAMgAAA==" + }, + { + "key": "success", + "annotation_type": "BOOL", + "value": "AQ==" + }, + { + "key": "processed", + "annotation_type": "DOUBLE", + "value": "P/gAAAAAAAA=" + } + ] + }, + { + "trace_id": 1068169210207794600, + "name": "checkStock", + "id": -129168404463703009, + "parent_id": 1068169210207794600, + "timestamp": 1544805927453923, + "duration": 3740, + "annotations": [] + } +] diff --git a/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v2_single.json b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v2_single.json new file mode 100644 index 00000000000..875eda1a0c5 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/testdata/zipkin_v2_single.json @@ -0,0 +1,38 @@ +[ + { + "traceId": "4d1e00c0db9010db86154a4ba6e91385", + "parentId": "86154a4ba6e91385", + "id": "4d1e00c0db9010db", + "kind": "CLIENT", + "name": "get", + "timestamp": 1472470996199000, + "duration": 207000, + "localEndpoint": { + "serviceName": "frontend", + "ipv6": "7::0.128.128.127" + }, + "remoteEndpoint": { + "serviceName": "backend", + "ipv4": "192.168.99.101", + "port": 9000 + }, + "annotations": [ + { + "timestamp": 1472470996238000, + "value": "foo" + }, + { + "timestamp": 1472470996403000, + "value": "bar" + } + ], + "tags": { + "http.path": "/api", + "http.status_code": "500", + "cache_hit": "true", + "ping_count": "25", + "timeout": "12.3", + "clnt/finagle.version": "6.45.0" + } + } +] diff --git a/internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2.go b/internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2.go new file mode 100644 index 00000000000..ad263129387 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2.go @@ -0,0 +1,351 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "strconv" + "time" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +var sampled = true + +// InternalTracesToZipkinSpans translates internal trace data into Zipkin v2 spans. +// Returns a slice of Zipkin SpanModel's. +func InternalTracesToZipkinSpans(td pdata.Traces) ([]*zipkinmodel.SpanModel, error) { + + resourceSpans := td.ResourceSpans() + if resourceSpans.Len() == 0 { + return nil, nil + } + + zSpans := make([]*zipkinmodel.SpanModel, 0, td.SpanCount()) + + for i := 0; i < resourceSpans.Len(); i++ { + batch, err := resourceSpansToZipkinSpans(resourceSpans.At(i), td.SpanCount()/resourceSpans.Len()) + if err != nil { + return zSpans, err + } + if batch != nil { + zSpans = append(zSpans, batch...) + } + } + + return zSpans, nil +} + +func resourceSpansToZipkinSpans(rs pdata.ResourceSpans, estSpanCount int) ([]*zipkinmodel.SpanModel, error) { + resource := rs.Resource() + ilss := rs.InstrumentationLibrarySpans() + + if resource.Attributes().Len() == 0 && ilss.Len() == 0 { + return nil, nil + } + + localServiceName, zTags := resourceToZipkinEndpointServiceNameAndAttributeMap(resource) + + zSpans := make([]*zipkinmodel.SpanModel, 0, estSpanCount) + for i := 0; i < ilss.Len(); i++ { + ils := ilss.At(i) + extractInstrumentationLibraryTags(ils.InstrumentationLibrary(), zTags) + spans := ils.Spans() + for j := 0; j < spans.Len(); j++ { + zSpan, err := spanToZipkinSpan(spans.At(j), localServiceName, zTags) + if err != nil { + return zSpans, err + } + zSpans = append(zSpans, zSpan) + } + } + + return zSpans, nil +} + +func extractInstrumentationLibraryTags(il pdata.InstrumentationLibrary, zTags map[string]string) { + if ilName := il.Name(); ilName != "" { + zTags[tracetranslator.TagInstrumentationName] = ilName + } + if ilVer := il.Version(); ilVer != "" { + zTags[tracetranslator.TagInstrumentationVersion] = ilVer + } +} + +func spanToZipkinSpan( + span pdata.Span, + localServiceName string, + zTags map[string]string, +) (*zipkinmodel.SpanModel, error) { + + tags := aggregateSpanTags(span, zTags) + + zs := &zipkinmodel.SpanModel{} + + if !span.TraceID().IsValid() { + return zs, errors.New("TraceID is invalid") + } + zs.TraceID = convertTraceID(span.TraceID()) + if !span.SpanID().IsValid() { + return zs, errors.New("SpanID is invalid") + } + zs.ID = convertSpanID(span.SpanID()) + + if len(span.TraceState()) > 0 { + tags[tracetranslator.TagW3CTraceState] = string(span.TraceState()) + } + + if span.ParentSpanID().IsValid() { + id := convertSpanID(span.ParentSpanID()) + zs.ParentID = &id + } + + zs.Sampled = &sampled + zs.Name = span.Name() + zs.Timestamp = pdata.UnixNanoToTime(span.StartTime()) + if span.EndTime() != 0 { + zs.Duration = time.Duration(span.EndTime() - span.StartTime()) + } + zs.Kind = spanKindToZipkinKind(span.Kind()) + if span.Kind() == pdata.SpanKindINTERNAL { + tags[tracetranslator.TagSpanKind] = "internal" + } + + redundantKeys := make(map[string]bool, 8) + zs.LocalEndpoint = zipkinEndpointFromTags(tags, localServiceName, false, redundantKeys) + zs.RemoteEndpoint = zipkinEndpointFromTags(tags, "", true, redundantKeys) + + removeRedundentTags(redundantKeys, tags) + + status := span.Status() + tags[tracetranslator.TagStatusCode] = status.Code().String() + if status.Message() != "" { + tags[tracetranslator.TagStatusMsg] = status.Message() + if int32(status.Code()) > 0 { + zs.Err = fmt.Errorf("%s", status.Message()) + } + } + + if err := spanEventsToZipkinAnnotations(span.Events(), zs); err != nil { + return nil, err + } + if err := spanLinksToZipkinTags(span.Links(), tags); err != nil { + return nil, err + } + + zs.Tags = tags + + return zs, nil +} + +func aggregateSpanTags(span pdata.Span, zTags map[string]string) map[string]string { + tags := make(map[string]string) + for key, val := range zTags { + tags[key] = val + } + spanTags := attributeMapToStringMap(span.Attributes()) + for key, val := range spanTags { + tags[key] = val + } + return tags +} + +func spanEventsToZipkinAnnotations(events pdata.SpanEventSlice, zs *zipkinmodel.SpanModel) error { + if events.Len() > 0 { + zAnnos := make([]zipkinmodel.Annotation, events.Len()) + for i := 0; i < events.Len(); i++ { + event := events.At(i) + if event.Attributes().Len() == 0 && event.DroppedAttributesCount() == 0 { + zAnnos[i] = zipkinmodel.Annotation{ + Timestamp: pdata.UnixNanoToTime(event.Timestamp()), + Value: event.Name(), + } + } else { + jsonStr, err := json.Marshal(tracetranslator.AttributeMapToMap(event.Attributes())) + if err != nil { + return err + } + zAnnos[i] = zipkinmodel.Annotation{ + Timestamp: pdata.UnixNanoToTime(event.Timestamp()), + Value: fmt.Sprintf(tracetranslator.SpanEventDataFormat, event.Name(), jsonStr, + event.DroppedAttributesCount()), + } + } + } + zs.Annotations = zAnnos + } + return nil +} + +func spanLinksToZipkinTags(links pdata.SpanLinkSlice, zTags map[string]string) error { + for i := 0; i < links.Len(); i++ { + link := links.At(i) + key := fmt.Sprintf("otlp.link.%d", i) + jsonStr, err := json.Marshal(tracetranslator.AttributeMapToMap(link.Attributes())) + if err != nil { + return err + } + zTags[key] = fmt.Sprintf(tracetranslator.SpanLinkDataFormat, link.TraceID().HexString(), + link.SpanID().HexString(), link.TraceState(), jsonStr, link.DroppedAttributesCount()) + } + return nil +} + +func attributeMapToStringMap(attrMap pdata.AttributeMap) map[string]string { + rawMap := make(map[string]string) + attrMap.ForEach(func(k string, v pdata.AttributeValue) { + rawMap[k] = tracetranslator.AttributeValueToString(v, false) + }) + return rawMap +} + +func removeRedundentTags(redundantKeys map[string]bool, zTags map[string]string) { + for k, v := range redundantKeys { + if v { + delete(zTags, k) + } + } +} + +func resourceToZipkinEndpointServiceNameAndAttributeMap( + resource pdata.Resource, +) (serviceName string, zTags map[string]string) { + zTags = make(map[string]string) + attrs := resource.Attributes() + if attrs.Len() == 0 { + return tracetranslator.ResourceNoServiceName, zTags + } + + attrs.ForEach(func(k string, v pdata.AttributeValue) { + zTags[k] = tracetranslator.AttributeValueToString(v, false) + }) + + serviceName = extractZipkinServiceName(zTags) + return serviceName, zTags +} + +func extractZipkinServiceName(zTags map[string]string) string { + var serviceName string + if sn, ok := zTags[conventions.AttributeServiceName]; ok { + serviceName = sn + delete(zTags, conventions.AttributeServiceName) + } else if fn, ok := zTags[conventions.AttributeFaasName]; ok { + serviceName = fn + delete(zTags, conventions.AttributeFaasName) + zTags[tracetranslator.TagServiceNameSource] = conventions.AttributeFaasName + } else if fn, ok := zTags[conventions.AttributeK8sDeployment]; ok { + serviceName = fn + delete(zTags, conventions.AttributeK8sDeployment) + zTags[tracetranslator.TagServiceNameSource] = conventions.AttributeK8sDeployment + } else if fn, ok := zTags[conventions.AttributeProcessExecutableName]; ok { + serviceName = fn + delete(zTags, conventions.AttributeProcessExecutableName) + zTags[tracetranslator.TagServiceNameSource] = conventions.AttributeProcessExecutableName + } else { + serviceName = tracetranslator.ResourceNoServiceName + } + return serviceName +} + +func spanKindToZipkinKind(kind pdata.SpanKind) zipkinmodel.Kind { + switch kind { + case pdata.SpanKindCLIENT: + return zipkinmodel.Client + case pdata.SpanKindSERVER: + return zipkinmodel.Server + case pdata.SpanKindPRODUCER: + return zipkinmodel.Producer + case pdata.SpanKindCONSUMER: + return zipkinmodel.Consumer + default: + return zipkinmodel.Undetermined + } +} + +func zipkinEndpointFromTags( + zTags map[string]string, + localServiceName string, + remoteEndpoint bool, + redundantKeys map[string]bool, +) (endpoint *zipkinmodel.Endpoint) { + + serviceName := localServiceName + if peerSvc, ok := zTags[conventions.AttributePeerService]; ok && remoteEndpoint { + serviceName = peerSvc + redundantKeys[conventions.AttributePeerService] = true + } + + var ipKey, portKey string + if remoteEndpoint { + ipKey, portKey = conventions.AttributeNetPeerIP, conventions.AttributeNetPeerPort + } else { + ipKey, portKey = conventions.AttributeNetHostIP, conventions.AttributeNetHostPort + } + + var ip net.IP + ipv6Selected := false + if ipStr, ok := zTags[ipKey]; ok { + ipv6Selected = isIPv6Address(ipStr) + ip = net.ParseIP(ipStr) + redundantKeys[ipKey] = true + } + + var port uint64 + if portStr, ok := zTags[portKey]; ok { + port, _ = strconv.ParseUint(portStr, 10, 16) + redundantKeys[portKey] = true + } + + if serviceName == "" && ip == nil { + return nil + } + + zEndpoint := &zipkinmodel.Endpoint{ + ServiceName: serviceName, + Port: uint16(port), + } + if ipv6Selected { + zEndpoint.IPv6 = ip + } else { + zEndpoint.IPv4 = ip + } + + return zEndpoint +} + +func isIPv6Address(ipStr string) bool { + for i := 0; i < len(ipStr); i++ { + if ipStr[i] == ':' { + return true + } + } + return false +} + +func convertTraceID(t pdata.TraceID) zipkinmodel.TraceID { + h, l := tracetranslator.TraceIDToUInt64Pair(t) + return zipkinmodel.TraceID{High: h, Low: l} +} + +func convertSpanID(s pdata.SpanID) zipkinmodel.ID { + return zipkinmodel.ID(tracetranslator.BytesToUInt64SpanID(s.Bytes())) +} diff --git a/internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2_test.go b/internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2_test.go new file mode 100644 index 00000000000..b3d5d66fb7b --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/traces_to_zipkinv2_test.go @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "errors" + "io" + "math/rand" + "testing" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/goldendataset" + "go.opentelemetry.io/collector/internal/testdata" +) + +func TestInternalTracesToZipkinSpans(t *testing.T) { + tests := []struct { + name string + td pdata.Traces + zs []*zipkinmodel.SpanModel + err error + }{ + { + name: "empty", + td: testdata.GenerateTraceDataEmpty(), + err: nil, + }, + { + name: "oneEmpty", + td: testdata.GenerateTraceDataOneEmptyResourceSpans(), + zs: make([]*zipkinmodel.SpanModel, 0), + err: nil, + }, + { + name: "noLibs", + td: testdata.GenerateTraceDataNoLibraries(), + zs: make([]*zipkinmodel.SpanModel, 0), + err: nil, + }, + { + name: "oneEmptyLib", + td: testdata.GenerateTraceDataOneEmptyInstrumentationLibrary(), + zs: make([]*zipkinmodel.SpanModel, 0), + err: nil, + }, + { + name: "oneSpanNoResrouce", + td: testdata.GenerateTraceDataOneSpanNoResource(), + zs: make([]*zipkinmodel.SpanModel, 0), + err: errors.New("TraceID is invalid"), + }, + { + name: "oneSpan", + td: generateTraceOneSpanOneTraceID(), + zs: []*zipkinmodel.SpanModel{zipkinOneSpan()}, + err: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + zss, err := InternalTracesToZipkinSpans(test.td) + assert.EqualValues(t, test.err, err) + if test.name == "empty" { + assert.Nil(t, zss) + } else { + assert.Equal(t, len(test.zs), len(zss)) + assert.EqualValues(t, test.zs, zss) + } + }) + } +} + +func TestInternalTracesToZipkinSpansAndBack(t *testing.T) { + rscSpans, err := goldendataset.GenerateResourceSpans( + "../../../internal/goldendataset/testdata/generated_pict_pairs_traces.txt", + "../../../internal/goldendataset/testdata/generated_pict_pairs_spans.txt", + io.Reader(rand.New(rand.NewSource(2004)))) + assert.NoError(t, err) + for _, rs := range rscSpans { + orig := make([]*otlptrace.ResourceSpans, 1) + orig[0] = rs + td := pdata.TracesFromOtlp(orig) + zipkinSpans, err := InternalTracesToZipkinSpans(td) + assert.NoError(t, err) + assert.Equal(t, td.SpanCount(), len(zipkinSpans)) + tdFromZS, zErr := V2SpansToInternalTraces(zipkinSpans, false) + assert.NoError(t, zErr, zipkinSpans) + assert.NotNil(t, tdFromZS) + assert.Equal(t, td.SpanCount(), tdFromZS.SpanCount()) + } +} + +func generateTraceOneSpanOneTraceID() pdata.Traces { + td := testdata.GenerateTraceDataOneSpan() + span := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + span.SetTraceID(pdata.NewTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})) + span.SetSpanID(pdata.NewSpanID([8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08})) + return td +} + +func zipkinOneSpan() *zipkinmodel.SpanModel { + trueBool := true + return &zipkinmodel.SpanModel{ + SpanContext: zipkinmodel.SpanContext{ + TraceID: zipkinmodel.TraceID{High: 72623859790382856, Low: 651345242494996240}, + ID: 72623859790382856, + ParentID: nil, + Debug: false, + Sampled: &trueBool, + Err: errors.New("status-cancelled"), + }, + LocalEndpoint: &zipkinmodel.Endpoint{ + ServiceName: "OTLPResourceNoServiceName", + }, + RemoteEndpoint: nil, + Annotations: []zipkinmodel.Annotation{ + { + Timestamp: testdata.TestSpanEventTime, + Value: "event-with-attr|{\"span-event-attr\":\"span-event-attr-val\"}|2", + }, + { + Timestamp: testdata.TestSpanEventTime, + Value: "event|{}|2", + }, + }, + Tags: map[string]string{ + "resource-attr": "resource-attr-val-1", + "status.code": "STATUS_CODE_ERROR", + "status.message": "status-cancelled", + }, + Name: "operationA", + Timestamp: testdata.TestSpanStartTime, + Duration: 1000000468, + Shared: false, + } +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan.go new file mode 100644 index 00000000000..b1dbb23dc2c --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan.go @@ -0,0 +1,267 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "math" + "net" + + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +// v1ThriftBatchToOCProto converts Zipkin v1 spans to OC Proto. +func v1ThriftBatchToOCProto(zSpans []*zipkincore.Span) ([]consumerdata.TraceData, error) { + ocSpansAndParsedAnnotations := make([]ocSpanAndParsedAnnotations, 0, len(zSpans)) + for _, zSpan := range zSpans { + ocSpan, parsedAnnotations := zipkinV1ThriftToOCSpan(zSpan) + ocSpansAndParsedAnnotations = append(ocSpansAndParsedAnnotations, ocSpanAndParsedAnnotations{ + ocSpan: ocSpan, + parsedAnnotations: parsedAnnotations, + }) + } + + return zipkinToOCProtoBatch(ocSpansAndParsedAnnotations) +} + +func zipkinV1ThriftToOCSpan(zSpan *zipkincore.Span) (*tracepb.Span, *annotationParseResult) { + traceIDHigh := int64(0) + if zSpan.TraceIDHigh != nil { + traceIDHigh = *zSpan.TraceIDHigh + } + + // TODO: (@pjanotti) ideally we should error here instead of generating invalid OC proto + // however per https://go.opentelemetry.io/collector/issues/349 + // failures on the receivers in general are silent at this moment, so letting them + // proceed for now. We should validate the traceID, spanID and parentID are good with + // OC proto requirements. + traceID := tracetranslator.Int64ToByteTraceID(traceIDHigh, zSpan.TraceID) + spanID := tracetranslator.Int64ToByteSpanID(zSpan.ID) + var parentID []byte + if zSpan.ParentID != nil { + parentIDBytes := tracetranslator.Int64ToByteSpanID(*zSpan.ParentID) + parentID = parentIDBytes[:] + } + + parsedAnnotations := parseZipkinV1ThriftAnnotations(zSpan.Annotations) + attributes, ocStatus, localComponent := zipkinV1ThriftBinAnnotationsToOCAttributes(zSpan.BinaryAnnotations) + if parsedAnnotations.Endpoint.ServiceName == unknownServiceName && localComponent != "" { + parsedAnnotations.Endpoint.ServiceName = localComponent + } + + var startTime, endTime *timestamppb.Timestamp + if zSpan.Timestamp == nil { + startTime = parsedAnnotations.EarlyAnnotationTime + endTime = parsedAnnotations.LateAnnotationTime + } else { + startTime = epochMicrosecondsToTimestamp(*zSpan.Timestamp) + var duration int64 + if zSpan.Duration != nil { + duration = *zSpan.Duration + } + endTime = epochMicrosecondsToTimestamp(*zSpan.Timestamp + duration) + } + + ocSpan := &tracepb.Span{ + TraceId: traceID[:], + SpanId: spanID[:], + ParentSpanId: parentID, + Status: ocStatus, + Kind: parsedAnnotations.Kind, + TimeEvents: parsedAnnotations.TimeEvents, + StartTime: startTime, + EndTime: endTime, + Attributes: attributes, + } + + if zSpan.Name != "" { + ocSpan.Name = &tracepb.TruncatableString{Value: zSpan.Name} + } + + setSpanKind(ocSpan, parsedAnnotations.Kind, parsedAnnotations.ExtendedKind) + + return ocSpan, parsedAnnotations +} + +func parseZipkinV1ThriftAnnotations(ztAnnotations []*zipkincore.Annotation) *annotationParseResult { + annotations := make([]*annotation, 0, len(ztAnnotations)) + for _, ztAnnot := range ztAnnotations { + annot := &annotation{ + Timestamp: ztAnnot.Timestamp, + Value: ztAnnot.Value, + Endpoint: toTranslatorEndpoint(ztAnnot.Host), + } + annotations = append(annotations, annot) + } + return parseZipkinV1Annotations(annotations) +} + +func toTranslatorEndpoint(e *zipkincore.Endpoint) *endpoint { + if e == nil { + return nil + } + + var ipv4, ipv6 string + if e.Ipv4 != 0 { + ipv4 = net.IPv4(byte(e.Ipv4>>24), byte(e.Ipv4>>16), byte(e.Ipv4>>8), byte(e.Ipv4)).String() + } + if len(e.Ipv6) != 0 { + ipv6 = net.IP(e.Ipv6).String() + } + return &endpoint{ + ServiceName: e.ServiceName, + IPv4: ipv4, + IPv6: ipv6, + Port: int32(e.Port), + } +} + +var trueByteSlice = []byte{1} + +func zipkinV1ThriftBinAnnotationsToOCAttributes(ztBinAnnotations []*zipkincore.BinaryAnnotation) (attributes *tracepb.Span_Attributes, status *tracepb.Status, fallbackServiceName string) { + if len(ztBinAnnotations) == 0 { + return nil, nil, "" + } + + sMapper := &statusMapper{} + var localComponent string + attributeMap := make(map[string]*tracepb.AttributeValue) + for _, binaryAnnotation := range ztBinAnnotations { + pbAttrib := &tracepb.AttributeValue{} + binAnnotationType := binaryAnnotation.AnnotationType + if binaryAnnotation.Host != nil { + fallbackServiceName = binaryAnnotation.Host.ServiceName + } + switch binaryAnnotation.AnnotationType { + case zipkincore.AnnotationType_BOOL: + isTrue := bytes.Equal(binaryAnnotation.Value, trueByteSlice) + pbAttrib.Value = &tracepb.AttributeValue_BoolValue{BoolValue: isTrue} + case zipkincore.AnnotationType_BYTES: + bytesStr := base64.StdEncoding.EncodeToString(binaryAnnotation.Value) + pbAttrib.Value = &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: bytesStr}} + case zipkincore.AnnotationType_DOUBLE: + if d, err := bytesFloat64ToFloat64(binaryAnnotation.Value); err != nil { + pbAttrib.Value = strAttributeForError(err) + } else { + pbAttrib.Value = &tracepb.AttributeValue_DoubleValue{DoubleValue: d} + } + case zipkincore.AnnotationType_I16: + if i, err := bytesInt16ToInt64(binaryAnnotation.Value); err != nil { + pbAttrib.Value = strAttributeForError(err) + } else { + pbAttrib.Value = &tracepb.AttributeValue_IntValue{IntValue: i} + } + case zipkincore.AnnotationType_I32: + if i, err := bytesInt32ToInt64(binaryAnnotation.Value); err != nil { + pbAttrib.Value = strAttributeForError(err) + } else { + pbAttrib.Value = &tracepb.AttributeValue_IntValue{IntValue: i} + } + case zipkincore.AnnotationType_I64: + if i, err := bytesInt64ToInt64(binaryAnnotation.Value); err != nil { + pbAttrib.Value = strAttributeForError(err) + } else { + pbAttrib.Value = &tracepb.AttributeValue_IntValue{IntValue: i} + } + case zipkincore.AnnotationType_STRING: + pbAttrib.Value = &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: string(binaryAnnotation.Value)}} + default: + err := fmt.Errorf("unknown zipkin v1 binary annotation type (%d)", int(binAnnotationType)) + pbAttrib.Value = strAttributeForError(err) + } + + key := binaryAnnotation.Key + if key == zipkincore.LOCAL_COMPONENT { + // TODO: (@pjanotti) add reference to OpenTracing and change related tags to use them + key = "component" + localComponent = string(binaryAnnotation.Value) + } + + if drop := sMapper.fromAttribute(key, pbAttrib); drop { + continue + } + + attributeMap[key] = pbAttrib + } + + status = sMapper.ocStatus() + + if len(attributeMap) == 0 { + return nil, status, "" + } + + if fallbackServiceName == "" { + fallbackServiceName = localComponent + } + + attributes = &tracepb.Span_Attributes{ + AttributeMap: attributeMap, + } + return attributes, status, fallbackServiceName +} + +var errNotEnoughBytes = errors.New("not enough bytes representing the number") + +func bytesInt16ToInt64(b []byte) (int64, error) { + const minSliceLength = 2 + if len(b) < minSliceLength { + return 0, errNotEnoughBytes + } + return int64(binary.BigEndian.Uint16(b[:minSliceLength])), nil +} + +func bytesInt32ToInt64(b []byte) (int64, error) { + const minSliceLength = 4 + if len(b) < minSliceLength { + return 0, errNotEnoughBytes + } + return int64(binary.BigEndian.Uint32(b[:minSliceLength])), nil +} + +func bytesInt64ToInt64(b []byte) (int64, error) { + const minSliceLength = 8 + if len(b) < minSliceLength { + return 0, errNotEnoughBytes + } + return int64(binary.BigEndian.Uint64(b[:minSliceLength])), nil +} + +func bytesFloat64ToFloat64(b []byte) (float64, error) { + const minSliceLength = 8 + if len(b) < minSliceLength { + return 0.0, errNotEnoughBytes + } + bits := binary.BigEndian.Uint64(b) + return math.Float64frombits(bits), nil +} + +func strAttributeForError(err error) *tracepb.AttributeValue_StringValue { + return &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{ + Value: "<" + err.Error() + ">", + }, + } +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan_test.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan_test.go new file mode 100644 index 00000000000..101ba74e3a6 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_protospan_test.go @@ -0,0 +1,619 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "encoding/binary" + "encoding/json" + "io/ioutil" + "math" + "sort" + "testing" + + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func TestZipkinThriftFallbackToLocalComponent(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_thrift_local_component.json") + require.NoError(t, err, "Failed to load test data") + + var ztSpans []*zipkincore.Span + err = json.Unmarshal(blob, &ztSpans) + require.NoError(t, err, "Failed to unmarshal json into zipkin v1 thrift") + + reqs, err := v1ThriftBatchToOCProto(ztSpans) + require.NoError(t, err, "Failed to translate zipkinv1 thrift to OC proto") + require.Equal(t, 2, len(reqs), "Invalid trace service requests count") + + // Ensure the order of nodes + sort.Slice(reqs, func(i, j int) bool { + return reqs[i].Node.ServiceInfo.Name < reqs[j].Node.ServiceInfo.Name + }) + + // First span didn't have a host/endpoint to give service name, use the local component. + got := reqs[0].Node.ServiceInfo.Name + require.Equal(t, "myLocalComponent", got) + + // Second span have a host/endpoint to give service name, do not use local component. + got = reqs[1].Node.ServiceInfo.Name + require.Equal(t, "myServiceName", got) +} + +func TestV1ThriftToOCProto(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_thrift_single_batch.json") + require.NoError(t, err, "Failed to load test data") + + var ztSpans []*zipkincore.Span + err = json.Unmarshal(blob, &ztSpans) + require.NoError(t, err, "Failed to unmarshal json into zipkin v1 thrift") + + got, err := v1ThriftBatchToOCProto(ztSpans) + require.NoError(t, err, "Failed to translate zipkinv1 thrift to OC proto") + + want := ocBatchesFromZipkinV1 + sortTraceByNodeName(want) + sortTraceByNodeName(got) + + assert.EqualValues(t, want, got) +} + +func BenchmarkV1ThriftToOCProto(b *testing.B) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_thrift_single_batch.json") + require.NoError(b, err, "Failed to load test data") + + var ztSpans []*zipkincore.Span + err = json.Unmarshal(blob, &ztSpans) + require.NoError(b, err, "Failed to unmarshal json into zipkin v1 thrift") + + for n := 0; n < b.N; n++ { + v1ThriftBatchToOCProto(ztSpans) + } +} + +func TestZipkinThriftAnnotationsToOCStatus(t *testing.T) { + type test struct { + haveTags []*zipkincore.BinaryAnnotation + wantAttributes *tracepb.Span_Attributes + wantStatus *tracepb.Status + } + + cases := []test{ + // too large code for OC + { + haveTags: []*zipkincore.BinaryAnnotation{{ + Key: "status.code", + Value: uint64ToBytes(math.MaxInt64), + AnnotationType: zipkincore.AnnotationType_I64, + }}, + wantAttributes: nil, + wantStatus: nil, + }, + // only status.code tag + { + haveTags: []*zipkincore.BinaryAnnotation{{ + Key: "status.code", + Value: uint64ToBytes(5), + AnnotationType: zipkincore.AnnotationType_I64, + }}, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 5, + }, + }, + { + haveTags: []*zipkincore.BinaryAnnotation{{ + Key: "status.code", + Value: uint32ToBytes(6), + AnnotationType: zipkincore.AnnotationType_I32, + }}, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 6, + }, + }, + { + haveTags: []*zipkincore.BinaryAnnotation{{ + Key: "status.code", + Value: uint16ToBytes(7), + AnnotationType: zipkincore.AnnotationType_I16, + }}, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 7, + }, + }, + // only status.message tag + { + haveTags: []*zipkincore.BinaryAnnotation{{ + Key: "status.message", + Value: []byte("Forbidden"), + AnnotationType: zipkincore.AnnotationType_STRING, + }}, + wantAttributes: nil, + wantStatus: nil, + }, + // both status.code and status.message + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "status.code", + Value: uint32ToBytes(13), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "status.message", + Value: []byte("Forbidden"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + }, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 13, + Message: "Forbidden", + }, + }, + + // http status.code + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "http.status_code", + Value: uint32ToBytes(404), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "http.status_message", + Value: []byte("NotFound"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 5, + Message: "NotFound", + }, + }, + + // http and oc + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "http.status_code", + Value: uint32ToBytes(404), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "http.status_message", + Value: []byte("NotFound"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + { + Key: "status.code", + Value: uint32ToBytes(13), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "status.message", + Value: []byte("Forbidden"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 13, + Message: "Forbidden", + }, + }, + + // http and only oc code + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "http.status_code", + Value: uint32ToBytes(404), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "http.status_message", + Value: []byte("NotFound"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + { + Key: "status.code", + Value: uint32ToBytes(14), + AnnotationType: zipkincore.AnnotationType_I32, + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 14, + }, + }, + // http and only oc message + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "http.status_code", + Value: uint32ToBytes(404), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "http.status_message", + Value: []byte("NotFound"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + { + Key: "status.message", + Value: []byte("Forbidden"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 5, + Message: "NotFound", + }, + }, + + // census tags + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "census.status_code", + Value: uint32ToBytes(18), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "census.status_description", + Value: []byte("RPCError"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + }, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 18, + Message: "RPCError", + }, + }, + + // census tags priority over others + { + haveTags: []*zipkincore.BinaryAnnotation{ + { + Key: "census.status_code", + Value: uint32ToBytes(18), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "census.status_description", + Value: []byte("RPCError"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + { + Key: "http.status_code", + Value: uint32ToBytes(404), + AnnotationType: zipkincore.AnnotationType_I32, + }, + { + Key: "http.status_message", + Value: []byte("NotFound"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + { + Key: "status.message", + Value: []byte("Forbidden"), + AnnotationType: zipkincore.AnnotationType_STRING, + }, + { + Key: "status.code", + Value: uint32ToBytes(1), + AnnotationType: zipkincore.AnnotationType_I32, + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 18, + Message: "RPCError", + }, + }, + } + + for i, c := range cases { + zSpans := []*zipkincore.Span{{ + ID: 1, + TraceID: 1, + BinaryAnnotations: c.haveTags, + }} + gb, err := v1ThriftBatchToOCProto(zSpans) + if err != nil { + t.Errorf("#%d: Unexpected error: %v", i, err) + continue + } + gs := gb[0].Spans[0] + require.Equal(t, c.wantAttributes, gs.Attributes, "Unsuccessful conversion %d", i) + require.Equal(t, c.wantStatus, gs.Status, "Unsuccessful conversion %d", i) + } +} + +func TestThriftHTTPToGRPCStatusCode(t *testing.T) { + for i := int32(100); i <= 600; i++ { + wantStatus := tracetranslator.OCStatusCodeFromHTTP(i) + gb, err := v1ThriftBatchToOCProto([]*zipkincore.Span{{ + ID: 1, + TraceID: 1, + BinaryAnnotations: []*zipkincore.BinaryAnnotation{ + { + Key: "http.status_code", + Value: uint32ToBytes(uint32(i)), + AnnotationType: zipkincore.AnnotationType_I32, + }, + }, + }}) + if err != nil { + t.Errorf("#%d: Unexpected error: %v", i, err) + continue + } + gs := gb[0].Spans[0] + require.Equal(t, wantStatus, gs.Status.Code, "Unsuccessful conversion %d", i) + } +} + +func Test_bytesInt16ToInt64(t *testing.T) { + tests := []struct { + name string + bytes []byte + want int64 + wantErr error + }{ + { + name: "too short byte slice", + bytes: nil, + want: 0, + wantErr: errNotEnoughBytes, + }, + { + name: "exact size byte slice", + bytes: []byte{0, 200}, + want: 200, + wantErr: nil, + }, + { + name: "large byte slice", + bytes: []byte{0, 128, 200, 200}, + want: 128, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := bytesInt16ToInt64(tt.bytes) + if err != tt.wantErr { + t.Errorf("bytesInt16ToInt64() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("bytesInt16ToInt64() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_bytesInt32ToInt64(t *testing.T) { + tests := []struct { + name string + bytes []byte + want int64 + wantErr error + }{ + { + name: "too short byte slice", + bytes: []byte{}, + want: 0, + wantErr: errNotEnoughBytes, + }, + { + name: "exact size byte slice", + bytes: []byte{0, 0, 0, 202}, + want: 202, + wantErr: nil, + }, + { + name: "large byte slice", + bytes: []byte{0, 0, 0, 128, 0, 0, 0, 0}, + want: 128, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := bytesInt32ToInt64(tt.bytes) + if err != tt.wantErr { + t.Errorf("bytesInt32ToInt64() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("bytesInt32ToInt64() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_bytesInt64ToInt64(t *testing.T) { + tests := []struct { + name string + bytes []byte + want int64 + wantErr error + }{ + { + name: "too short byte slice", + bytes: []byte{0, 0, 0, 0}, + want: 0, + wantErr: errNotEnoughBytes, + }, + { + name: "exact size byte slice", + bytes: []byte{0, 0, 0, 0, 0, 0, 0, 202}, + want: 202, + wantErr: nil, + }, + { + name: "large byte slice", + bytes: []byte{0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0}, + want: 128, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := bytesInt64ToInt64(tt.bytes) + if err != tt.wantErr { + t.Errorf("bytesInt64ToInt64() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("bytesInt64ToInt64() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_bytesFloat64ToFloat64(t *testing.T) { + tests := []struct { + name string + bytes []byte + want float64 + wantErr error + }{ + { + name: "too short byte slice", + bytes: []byte{0, 0, 0, 0}, + want: 0, + wantErr: errNotEnoughBytes, + }, + { + name: "exact size byte slice", + bytes: []byte{64, 9, 33, 251, 84, 68, 45, 24}, + want: 3.141592653589793, + wantErr: nil, + }, + { + name: "large byte slice", + bytes: []byte{64, 9, 33, 251, 84, 68, 45, 24, 0, 0, 0, 0}, + want: 3.141592653589793, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := bytesFloat64ToFloat64(tt.bytes) + if err != tt.wantErr { + t.Errorf("bytesFloat64ToFloat64() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("bytesFloat64ToFloat64() = %v, want %v", got, tt.want) + } + }) + } +} + +func uint64ToBytes(i uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, i) + return b +} + +func uint32ToBytes(i uint32) []byte { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, i) + return b +} + +func uint16ToBytes(i uint16) []byte { + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, i) + return b +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces.go new file mode 100644 index 00000000000..7c3fa6d7fb2 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/internaldata" +) + +func V1ThriftBatchToInternalTraces(zSpans []*zipkincore.Span) (pdata.Traces, error) { + traces := pdata.NewTraces() + + ocTraces, _ := v1ThriftBatchToOCProto(zSpans) + + for _, td := range ocTraces { + tmp := internaldata.OCToTraceData(td) + tmp.ResourceSpans().MoveAndAppendTo(traces.ResourceSpans()) + } + return traces, nil +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces_test.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces_test.go new file mode 100644 index 00000000000..c019ee7c8cb --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_thrift_to_traces_test.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "encoding/json" + "io/ioutil" + "testing" + + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestV1ThriftToTraces(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_thrift_single_batch.json") + require.NoError(t, err, "Failed to load test data") + + var ztSpans []*zipkincore.Span + err = json.Unmarshal(blob, &ztSpans) + require.NoError(t, err, "Failed to unmarshal json into zipkin v1 thrift") + + got, err := V1ThriftBatchToInternalTraces(ztSpans) + require.NoError(t, err, "Failed to translate zipkinv1 thrift to OC proto") + + assert.Equal(t, 5, got.SpanCount()) +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan.go new file mode 100644 index 00000000000..27863fe8c1c --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan.go @@ -0,0 +1,517 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "strconv" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/jaegertracing/jaeger/thrift-gen/zipkincore" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +var ( + // ZipkinV1 friendly conversion errors + msgZipkinV1JSONUnmarshalError = "zipkinv1" + msgZipkinV1TraceIDError = "zipkinV1 span traceId" + msgZipkinV1SpanIDError = "zipkinV1 span id" + msgZipkinV1ParentIDError = "zipkinV1 span parentId" + // Generic hex to ID conversion errors + errHexTraceIDWrongLen = errors.New("hex traceId span has wrong length (expected 16 or 32)") + errHexTraceIDParsing = errors.New("failed to parse hex traceId") + errHexTraceIDZero = errors.New("traceId is zero") + errHexIDWrongLen = errors.New("hex Id has wrong length (expected 16)") + errHexIDParsing = errors.New("failed to parse hex Id") + errHexIDZero = errors.New("ID is zero") +) + +// Trace translation from Zipkin V1 is a bit of special case since there is no model +// defined in golang for Zipkin V1 spans and there is no need to define one here, given +// that the zipkinV1Span defined below is as defined at: +// https://zipkin.io/zipkin-api/zipkin-api.yaml +type zipkinV1Span struct { + TraceID string `json:"traceId"` + Name string `json:"name,omitempty"` + ParentID string `json:"parentId,omitempty"` + ID string `json:"id"` + Timestamp int64 `json:"timestamp"` + Duration int64 `json:"duration"` + Debug bool `json:"debug,omitempty"` + Annotations []*annotation `json:"annotations,omitempty"` + BinaryAnnotations []*binaryAnnotation `json:"binaryAnnotations,omitempty"` +} + +// endpoint structure used by zipkinV1Span. +type endpoint struct { + ServiceName string `json:"serviceName"` + IPv4 string `json:"ipv4"` + IPv6 string `json:"ipv6"` + Port int32 `json:"port"` +} + +// annotation struct used by zipkinV1Span. +type annotation struct { + Timestamp int64 `json:"timestamp"` + Value string `json:"value"` + Endpoint *endpoint `json:"endpoint"` +} + +// binaryAnnotation used by zipkinV1Span. +type binaryAnnotation struct { + Key string `json:"key"` + Value string `json:"value"` + Endpoint *endpoint `json:"endpoint"` +} + +// v1JSONBatchToOCProto converts a JSON blob with a list of Zipkin v1 spans to OC Proto. +func v1JSONBatchToOCProto(blob []byte, parseStringTags bool) ([]consumerdata.TraceData, error) { + var zSpans []*zipkinV1Span + if err := json.Unmarshal(blob, &zSpans); err != nil { + return nil, fmt.Errorf("%s: %w", msgZipkinV1JSONUnmarshalError, err) + } + + ocSpansAndParsedAnnotations := make([]ocSpanAndParsedAnnotations, 0, len(zSpans)) + for _, zSpan := range zSpans { + ocSpan, parsedAnnotations, err := zipkinV1ToOCSpan(zSpan, parseStringTags) + if err != nil { + // error from internal package function, it already wraps the error to give better context. + return nil, err + } + ocSpansAndParsedAnnotations = append(ocSpansAndParsedAnnotations, ocSpanAndParsedAnnotations{ + ocSpan: ocSpan, + parsedAnnotations: parsedAnnotations, + }) + } + + return zipkinToOCProtoBatch(ocSpansAndParsedAnnotations) +} + +type ocSpanAndParsedAnnotations struct { + ocSpan *tracepb.Span + parsedAnnotations *annotationParseResult +} + +func zipkinToOCProtoBatch(ocSpansAndParsedAnnotations []ocSpanAndParsedAnnotations) ([]consumerdata.TraceData, error) { + // Service to batch maps the service name to the trace request with the corresponding node. + svcToTD := make(map[string]*consumerdata.TraceData) + for _, curr := range ocSpansAndParsedAnnotations { + req := getOrCreateNodeRequest(svcToTD, curr.parsedAnnotations.Endpoint) + req.Spans = append(req.Spans, curr.ocSpan) + } + + tds := make([]consumerdata.TraceData, 0, len(svcToTD)) + for _, v := range svcToTD { + tds = append(tds, *v) + } + return tds, nil +} + +func zipkinV1ToOCSpan(zSpan *zipkinV1Span, parseStringTags bool) (*tracepb.Span, *annotationParseResult, error) { + traceID, err := hexTraceIDToOCTraceID(zSpan.TraceID) + if err != nil { + return nil, nil, fmt.Errorf("%s: %w", msgZipkinV1TraceIDError, err) + } + spanID, err := hexIDToOCID(zSpan.ID) + if err != nil { + return nil, nil, fmt.Errorf("%s: %w", msgZipkinV1SpanIDError, err) + } + var parentID []byte + if zSpan.ParentID != "" { + id, err := hexIDToOCID(zSpan.ParentID) + if err != nil { + return nil, nil, fmt.Errorf("%s: %w", msgZipkinV1ParentIDError, err) + } + parentID = id + } + + parsedAnnotations := parseZipkinV1Annotations(zSpan.Annotations) + attributes, ocStatus, localComponent := zipkinV1BinAnnotationsToOCAttributes(zSpan.BinaryAnnotations, parseStringTags) + if parsedAnnotations.Endpoint.ServiceName == unknownServiceName && localComponent != "" { + parsedAnnotations.Endpoint.ServiceName = localComponent + } + var startTime, endTime *timestamppb.Timestamp + if zSpan.Timestamp == 0 { + startTime = parsedAnnotations.EarlyAnnotationTime + endTime = parsedAnnotations.LateAnnotationTime + } else { + startTime = epochMicrosecondsToTimestamp(zSpan.Timestamp) + endTime = epochMicrosecondsToTimestamp(zSpan.Timestamp + zSpan.Duration) + } + + ocSpan := &tracepb.Span{ + TraceId: traceID, + SpanId: spanID, + ParentSpanId: parentID, + Status: ocStatus, + Kind: parsedAnnotations.Kind, + TimeEvents: parsedAnnotations.TimeEvents, + StartTime: startTime, + EndTime: endTime, + Attributes: attributes, + } + + if zSpan.Name != "" { + ocSpan.Name = &tracepb.TruncatableString{Value: zSpan.Name} + } + + setSpanKind(ocSpan, parsedAnnotations.Kind, parsedAnnotations.ExtendedKind) + setTimestampsIfUnset(ocSpan) + + return ocSpan, parsedAnnotations, nil +} + +func setSpanKind(ocSpan *tracepb.Span, kind tracepb.Span_SpanKind, extendedKind tracetranslator.OpenTracingSpanKind) { + if kind == tracepb.Span_SPAN_KIND_UNSPECIFIED && + extendedKind != tracetranslator.OpenTracingSpanKindUnspecified { + // Span kind has no equivalent in OC, so we cannot represent it in the Kind field. + // We will set a TagSpanKind attribute in the span. This will successfully transfer + // in the pipeline until it reaches the exporter which is responsible for + // reverse translation. + if ocSpan.Attributes == nil { + ocSpan.Attributes = &tracepb.Span_Attributes{} + } + if ocSpan.Attributes.AttributeMap == nil { + ocSpan.Attributes.AttributeMap = make(map[string]*tracepb.AttributeValue, 1) + } + ocSpan.Attributes.AttributeMap[tracetranslator.TagSpanKind] = + &tracepb.AttributeValue{Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: string(extendedKind)}, + }} + } +} + +func zipkinV1BinAnnotationsToOCAttributes(binAnnotations []*binaryAnnotation, parseStringTags bool) (attributes *tracepb.Span_Attributes, status *tracepb.Status, fallbackServiceName string) { + if len(binAnnotations) == 0 { + return nil, nil, "" + } + + sMapper := &statusMapper{} + var localComponent string + attributeMap := make(map[string]*tracepb.AttributeValue) + for _, binAnnotation := range binAnnotations { + + if binAnnotation.Endpoint != nil && binAnnotation.Endpoint.ServiceName != "" { + fallbackServiceName = binAnnotation.Endpoint.ServiceName + } + + pbAttrib := parseAnnotationValue(binAnnotation.Value, parseStringTags) + + key := binAnnotation.Key + + if key == zipkincore.LOCAL_COMPONENT { + // TODO: (@pjanotti) add reference to OpenTracing and change related tags to use them + key = "component" + localComponent = binAnnotation.Value + } + + if drop := sMapper.fromAttribute(key, pbAttrib); drop { + continue + } + + attributeMap[key] = pbAttrib + } + + status = sMapper.ocStatus() + + if len(attributeMap) == 0 { + return nil, status, "" + } + + if fallbackServiceName == "" { + fallbackServiceName = localComponent + } + + attributes = &tracepb.Span_Attributes{ + AttributeMap: attributeMap, + } + + return attributes, status, fallbackServiceName +} + +func parseAnnotationValue(value string, parseStringTags bool) *tracepb.AttributeValue { + pbAttrib := &tracepb.AttributeValue{} + + if parseStringTags { + switch tracetranslator.DetermineValueType(value, false) { + case pdata.AttributeValueINT: + iValue, _ := strconv.ParseInt(value, 10, 64) + pbAttrib.Value = &tracepb.AttributeValue_IntValue{IntValue: iValue} + case pdata.AttributeValueDOUBLE: + fValue, _ := strconv.ParseFloat(value, 64) + pbAttrib.Value = &tracepb.AttributeValue_DoubleValue{DoubleValue: fValue} + case pdata.AttributeValueBOOL: + bValue, _ := strconv.ParseBool(value) + pbAttrib.Value = &tracepb.AttributeValue_BoolValue{BoolValue: bValue} + default: + pbAttrib.Value = &tracepb.AttributeValue_StringValue{StringValue: &tracepb.TruncatableString{Value: value}} + } + } else { + pbAttrib.Value = &tracepb.AttributeValue_StringValue{StringValue: &tracepb.TruncatableString{Value: value}} + } + + return pbAttrib +} + +// annotationParseResult stores the results of examining the original annotations, +// this way multiple passes on the annotations are not needed. +type annotationParseResult struct { + Endpoint *endpoint + TimeEvents *tracepb.Span_TimeEvents + Kind tracepb.Span_SpanKind + ExtendedKind tracetranslator.OpenTracingSpanKind + EarlyAnnotationTime *timestamppb.Timestamp + LateAnnotationTime *timestamppb.Timestamp +} + +// Unknown service name works both as a default value and a flag to indicate that a valid endpoint was found. +const unknownServiceName = "unknown-service" + +func parseZipkinV1Annotations(annotations []*annotation) *annotationParseResult { + // Zipkin V1 annotations have a timestamp so they fit well with OC TimeEvent + earlyAnnotationTimestamp := int64(math.MaxInt64) + lateAnnotationTimestamp := int64(math.MinInt64) + res := &annotationParseResult{} + timeEvents := make([]*tracepb.Span_TimeEvent, 0, len(annotations)) + + // We want to set the span kind from the first annotation that contains information + // about the span kind. This flags ensures we only set span kind once from + // the first annotation. + spanKindIsSet := false + + for _, currAnnotation := range annotations { + if currAnnotation == nil || currAnnotation.Value == "" { + continue + } + + endpointName := unknownServiceName + if currAnnotation.Endpoint != nil && currAnnotation.Endpoint.ServiceName != "" { + endpointName = currAnnotation.Endpoint.ServiceName + } + + // Check if annotation has span kind information. + annotationHasSpanKind := false + switch currAnnotation.Value { + case "cs", "cr", "ms", "mr", "ss", "sr": + annotationHasSpanKind = true + } + + // Populate the endpoint if it is not already populated and current endpoint + // has a service name and span kind. + if res.Endpoint == nil && endpointName != unknownServiceName && annotationHasSpanKind { + res.Endpoint = currAnnotation.Endpoint + } + + if !spanKindIsSet && annotationHasSpanKind { + // We have not yet populated span kind, do it now. + // Translate from Zipkin span kind stored in Value field to Kind/ExternalKind + // pair of internal fields. + switch currAnnotation.Value { + case "cs", "cr": + res.Kind = tracepb.Span_CLIENT + res.ExtendedKind = tracetranslator.OpenTracingSpanKindClient + + case "ms": + // "ms" and "mr" are PRODUCER and CONSUMER kinds which have no equivalent + // representation in OC. We keep res.Kind unspecified and will use + // ExtendedKind for translations. + res.ExtendedKind = tracetranslator.OpenTracingSpanKindProducer + + case "mr": + res.ExtendedKind = tracetranslator.OpenTracingSpanKindConsumer + + case "ss", "sr": + res.Kind = tracepb.Span_SERVER + res.ExtendedKind = tracetranslator.OpenTracingSpanKindServer + } + + // Remember that we populated the span kind, so that we don't do it again. + spanKindIsSet = true + } + + ts := epochMicrosecondsToTimestamp(currAnnotation.Timestamp) + if currAnnotation.Timestamp < earlyAnnotationTimestamp { + earlyAnnotationTimestamp = currAnnotation.Timestamp + res.EarlyAnnotationTime = ts + } + if currAnnotation.Timestamp > lateAnnotationTimestamp { + lateAnnotationTimestamp = currAnnotation.Timestamp + res.LateAnnotationTime = ts + } + + if annotationHasSpanKind { + // If this annotation is for the send/receive timestamps, no need to create the annotation + continue + } + + timeEvent := &tracepb.Span_TimeEvent{ + Time: ts, + // More economically we could use a tracepb.Span_TimeEvent_Message, however, it will mean the loss of some information. + // Using the more expensive annotation until/if something cheaper is needed. + Value: &tracepb.Span_TimeEvent_Annotation_{ + Annotation: &tracepb.Span_TimeEvent_Annotation{ + Description: &tracepb.TruncatableString{Value: currAnnotation.Value}, + }, + }, + } + + timeEvents = append(timeEvents, timeEvent) + } + + if len(timeEvents) > 0 { + res.TimeEvents = &tracepb.Span_TimeEvents{TimeEvent: timeEvents} + } + + if res.Endpoint == nil { + res.Endpoint = &endpoint{ + ServiceName: unknownServiceName, + } + } + + return res +} + +func hexTraceIDToOCTraceID(hex string) ([]byte, error) { + // Per info at https://zipkin.io/zipkin-api/zipkin-api.yaml it should be 16 or 32 characters + hexLen := len(hex) + if hexLen != 16 && hexLen != 32 { + return nil, errHexTraceIDWrongLen + } + + var high, low uint64 + var err error + if hexLen == 32 { + if high, err = strconv.ParseUint(hex[:16], 16, 64); err != nil { + return nil, errHexTraceIDParsing + } + } + + if low, err = strconv.ParseUint(hex[hexLen-16:], 16, 64); err != nil { + return nil, errHexTraceIDParsing + } + + if high == 0 && low == 0 { + return nil, errHexTraceIDZero + } + + tidBytes := tracetranslator.UInt64ToByteTraceID(high, low) + return tidBytes[:], nil +} + +func hexIDToOCID(hex string) ([]byte, error) { + // Per info at https://zipkin.io/zipkin-api/zipkin-api.yaml it should be 16 characters + if len(hex) != 16 { + return nil, errHexIDWrongLen + } + + idValue, err := strconv.ParseUint(hex, 16, 64) + if err != nil { + return nil, errHexIDParsing + } + + if idValue == 0 { + return nil, errHexIDZero + } + + idBytes := tracetranslator.UInt64ToByteSpanID(idValue) + return idBytes[:], nil +} + +func epochMicrosecondsToTimestamp(msecs int64) *timestamppb.Timestamp { + if msecs <= 0 { + return nil + } + t := ×tamppb.Timestamp{} + t.Seconds = msecs / 1e6 + t.Nanos = int32(msecs%1e6) * 1e3 + return t +} + +func getOrCreateNodeRequest(m map[string]*consumerdata.TraceData, endpoint *endpoint) *consumerdata.TraceData { + // this private function assumes that the caller never passes an nil endpoint + nodeKey := endpoint.string() + req := m[nodeKey] + + if req != nil { + return req + } + + req = &consumerdata.TraceData{ + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: endpoint.ServiceName}, + }, + } + + if attributeMap := endpoint.createAttributeMap(); attributeMap != nil { + req.Node.Attributes = attributeMap + } + + m[nodeKey] = req + + return req +} + +func (ep *endpoint) string() string { + return fmt.Sprintf("%s-%s-%s-%d", ep.ServiceName, ep.IPv4, ep.IPv6, ep.Port) +} + +func (ep *endpoint) createAttributeMap() map[string]string { + if ep.IPv4 == "" && ep.IPv6 == "" && ep.Port == 0 { + return nil + } + + attributeMap := make(map[string]string, 3) + if ep.IPv4 != "" { + attributeMap["ipv4"] = ep.IPv4 + } + if ep.IPv6 != "" { + attributeMap["ipv6"] = ep.IPv6 + } + if ep.Port != 0 { + attributeMap["port"] = strconv.Itoa(int(ep.Port)) + } + return attributeMap +} + +func setTimestampsIfUnset(span *tracepb.Span) { + // zipkin allows timestamp to be unset, but opentelemetry-collector expects it to have a value. + // If this is unset, the conversion from open census to the internal trace format breaks + // what should be an identity transformation oc -> internal -> oc + if span.StartTime == nil { + now := timestamppb.New(time.Now()) + span.StartTime = now + span.EndTime = now + + if span.Attributes == nil { + span.Attributes = &tracepb.Span_Attributes{} + } + if span.Attributes.AttributeMap == nil { + span.Attributes.AttributeMap = make(map[string]*tracepb.AttributeValue, 1) + } + span.Attributes.AttributeMap[StartTimeAbsent] = &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_BoolValue{ + BoolValue: true, + }} + } +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan_test.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan_test.go new file mode 100644 index 00000000000..44455959187 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_protospan_test.go @@ -0,0 +1,790 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "encoding/json" + "io/ioutil" + "sort" + "strconv" + "testing" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" + "github.com/google/go-cmp/cmp" + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + "go.opentelemetry.io/collector/consumer/consumerdata" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +func Test_hexIDToOCID(t *testing.T) { + tests := []struct { + name string + hexStr string + want []byte + wantErr error + }{ + { + name: "empty hex string", + hexStr: "", + want: nil, + wantErr: errHexIDWrongLen, + }, + { + name: "wrong length", + hexStr: "0000", + want: nil, + wantErr: errHexIDWrongLen, + }, + { + name: "parse error", + hexStr: "000000000000000-", + want: nil, + wantErr: errHexIDParsing, + }, + { + name: "all zero", + hexStr: "0000000000000000", + want: nil, + wantErr: errHexIDZero, + }, + { + name: "happy path", + hexStr: "0706050400010203", + want: []byte{7, 6, 5, 4, 0, 1, 2, 3}, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := hexIDToOCID(tt.hexStr) + require.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_hexTraceIDToOCTraceID(t *testing.T) { + tests := []struct { + name string + hexStr string + want []byte + wantErr error + }{ + { + name: "empty hex string", + hexStr: "", + want: nil, + wantErr: errHexTraceIDWrongLen, + }, + { + name: "wrong length", + hexStr: "000000000000000010", + want: nil, + wantErr: errHexTraceIDWrongLen, + }, + { + name: "parse error", + hexStr: "000000000000000X0000000000000000", + want: nil, + wantErr: errHexTraceIDParsing, + }, + { + name: "all zero", + hexStr: "00000000000000000000000000000000", + want: nil, + wantErr: errHexTraceIDZero, + }, + { + name: "happy path", + hexStr: "00000000000000010000000000000002", + want: []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2}, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := hexTraceIDToOCTraceID(tt.hexStr) + require.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestZipkinJSONFallbackToLocalComponent(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_local_component.json") + require.NoError(t, err, "Failed to load test data") + + reqs, err := v1JSONBatchToOCProto(blob, false) + require.NoError(t, err, "Failed to translate zipkinv1 to OC proto") + require.Equal(t, 2, len(reqs), "Invalid trace service requests count") + + // Ensure the order of nodes + sort.Slice(reqs, func(i, j int) bool { + return reqs[i].Node.ServiceInfo.Name < reqs[j].Node.ServiceInfo.Name + }) + + // First span didn't have a host/endpoint to give service name, use the local component. + got := reqs[0].Node.ServiceInfo.Name + require.Equal(t, "myLocalComponent", got) + + // Second span have a host/endpoint to give service name, do not use local component. + got = reqs[1].Node.ServiceInfo.Name + require.Equal(t, "myServiceName", got) +} + +func TestSingleJSONV1BatchToOCProto(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_single_batch.json") + require.NoError(t, err, "Failed to load test data") + + parseStringTags := true // This test relies on parsing int/bool to the typed span attributes + got, err := v1JSONBatchToOCProto(blob, parseStringTags) + require.NoError(t, err, "Failed to translate zipkinv1 to OC proto") + + want := ocBatchesFromZipkinV1 + sortTraceByNodeName(want) + sortTraceByNodeName(got) + + assert.EqualValues(t, got, want) +} + +func TestMultipleJSONV1BatchesToOCProto(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_multiple_batches.json") + require.NoError(t, err, "Failed to load test data") + + var batches []interface{} + err = json.Unmarshal(blob, &batches) + require.NoError(t, err, "Failed to load the batches") + + nodeToTraceReqs := make(map[string]*consumerdata.TraceData) + var got []consumerdata.TraceData + for _, batch := range batches { + jsonBatch, err := json.Marshal(batch) + require.NoError(t, err, "Failed to marshal interface back to blob") + + parseStringTags := true // This test relies on parsing int/bool to the typed span attributes + g, err := v1JSONBatchToOCProto(jsonBatch, parseStringTags) + require.NoError(t, err, "Failed to translate zipkinv1 to OC proto") + + // Coalesce the nodes otherwise they will differ due to multiple + // nodes representing same logical service + for i := range g { + key := g[i].Node.String() + if pTsr, ok := nodeToTraceReqs[key]; ok { + pTsr.Spans = append(pTsr.Spans, g[i].Spans...) + } else { + nodeToTraceReqs[key] = &g[i] + } + } + } + + for _, tsr := range nodeToTraceReqs { + got = append(got, *tsr) + } + + want := ocBatchesFromZipkinV1 + sortTraceByNodeName(want) + sortTraceByNodeName(got) + + if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { + t.Errorf("Unexpected difference:\n%v", diff) + } +} + +func sortTraceByNodeName(trace []consumerdata.TraceData) { + sort.Slice(trace, func(i, j int) bool { + return trace[i].Node.ServiceInfo.Name < trace[j].Node.ServiceInfo.Name + }) +} + +func TestZipkinAnnotationsToOCStatus(t *testing.T) { + type test struct { + name string + haveTags []*binaryAnnotation + wantAttributes *tracepb.Span_Attributes + wantStatus *tracepb.Status + } + + cases := []test{ + { + name: "only status.code tag", + haveTags: []*binaryAnnotation{{ + Key: "status.code", + Value: "13", + }}, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 13, + }, + }, + + { + name: "only status.message tag", + haveTags: []*binaryAnnotation{{ + Key: "status.message", + Value: "Forbidden", + }}, + wantAttributes: nil, + wantStatus: nil, + }, + + { + name: "both status.code and status.message", + haveTags: []*binaryAnnotation{ + { + Key: "status.code", + Value: "13", + }, + { + Key: "status.message", + Value: "Forbidden", + }, + }, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 13, + Message: "Forbidden", + }, + }, + + { + name: "http status.code", + haveTags: []*binaryAnnotation{ + { + Key: "http.status_code", + Value: "404", + }, + { + Key: "http.status_message", + Value: "NotFound", + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 5, + Message: "NotFound", + }, + }, + + { + name: "http and oc", + haveTags: []*binaryAnnotation{ + { + Key: "http.status_code", + Value: "404", + }, + { + Key: "http.status_message", + Value: "NotFound", + }, + { + Key: "status.code", + Value: "13", + }, + { + Key: "status.message", + Value: "Forbidden", + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 13, + Message: "Forbidden", + }, + }, + + { + name: "http and only oc code", + haveTags: []*binaryAnnotation{ + { + Key: "http.status_code", + Value: "404", + }, + { + Key: "http.status_message", + Value: "NotFound", + }, + { + Key: "status.code", + Value: "14", + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 14, + }, + }, + + { + name: "http and only oc message", + haveTags: []*binaryAnnotation{ + { + Key: "http.status_code", + Value: "404", + }, + { + Key: "http.status_message", + Value: "NotFound", + }, + { + Key: "status.message", + Value: "Forbidden", + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 5, + Message: "NotFound", + }, + }, + + { + name: "census tags", + haveTags: []*binaryAnnotation{ + { + Key: "census.status_code", + Value: "10", + }, + { + Key: "census.status_description", + Value: "RPCError", + }, + }, + wantAttributes: nil, + wantStatus: &tracepb.Status{ + Code: 10, + Message: "RPCError", + }, + }, + + { + name: "census tags priority over others", + haveTags: []*binaryAnnotation{ + { + Key: "census.status_code", + Value: "10", + }, + { + Key: "census.status_description", + Value: "RPCError", + }, + { + Key: "http.status_code", + Value: "404", + }, + { + Key: "http.status_message", + Value: "NotFound", + }, + { + Key: "status.message", + Value: "Forbidden", + }, + { + Key: "status.code", + Value: "7", + }, + }, + wantAttributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + tracetranslator.TagHTTPStatusCode: { + Value: &tracepb.AttributeValue_IntValue{ + IntValue: 404, + }, + }, + tracetranslator.TagHTTPStatusMsg: { + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: "NotFound"}, + }, + }, + }, + }, + wantStatus: &tracepb.Status{ + Code: 10, + Message: "RPCError", + }, + }, + } + + fakeTraceID := "00000000000000010000000000000002" + fakeSpanID := "0000000000000001" + + for i, c := range cases { + t.Run(c.name, func(t *testing.T) { + zSpans := []*zipkinV1Span{{ + ID: fakeSpanID, + TraceID: fakeTraceID, + BinaryAnnotations: c.haveTags, + Timestamp: 1, + }} + zBytes, err := json.Marshal(zSpans) + if err != nil { + t.Errorf("#%d: Unexpected error: %v", i, err) + return + } + + parseStringTags := true // This test relies on parsing int/bool to the typed span attributes + gb, err := v1JSONBatchToOCProto(zBytes, parseStringTags) + if err != nil { + t.Errorf("#%d: Unexpected error: %v", i, err) + return + } + gs := gb[0].Spans[0] + require.Equal(t, c.wantAttributes, gs.Attributes, "Unsuccessful conversion %d", i) + require.Equal(t, c.wantStatus, gs.Status, "Unsuccessful conversion %d", i) + }) + } +} + +func TestSpanWithoutTimestampGetsTag(t *testing.T) { + fakeTraceID := "00000000000000010000000000000002" + fakeSpanID := "0000000000000001" + zSpans := []*zipkinV1Span{ + { + ID: fakeSpanID, + TraceID: fakeTraceID, + Timestamp: 0, // no timestamp field + }, + } + zBytes, err := json.Marshal(zSpans) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + testStart := time.Now() + + gb, err := v1JSONBatchToOCProto(zBytes, false) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + gs := gb[0].Spans[0] + assert.NotNil(t, gs.StartTime) + assert.NotNil(t, gs.EndTime) + + assert.True(t, gs.StartTime.AsTime().Sub(testStart) >= 0) + + wantAttributes := &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + StartTimeAbsent: { + Value: &tracepb.AttributeValue_BoolValue{ + BoolValue: true, + }, + }, + }, + } + + assert.EqualValues(t, gs.Attributes, wantAttributes) +} + +func TestJSONHTTPToGRPCStatusCode(t *testing.T) { + fakeTraceID := "00000000000000010000000000000002" + fakeSpanID := "0000000000000001" + for i := int32(100); i <= 600; i++ { + wantStatus := tracetranslator.OCStatusCodeFromHTTP(i) + zBytes, err := json.Marshal([]*zipkinV1Span{{ + ID: fakeSpanID, + TraceID: fakeTraceID, + BinaryAnnotations: []*binaryAnnotation{ + { + Key: "http.status_code", + Value: strconv.Itoa(int(i)), + }, + }, + }}) + if err != nil { + t.Errorf("#%d: Unexpected error: %v", i, err) + continue + } + gb, err := v1JSONBatchToOCProto(zBytes, false) + if err != nil { + t.Errorf("#%d: Unexpected error: %v", i, err) + continue + } + + gs := gb[0].Spans[0] + require.Equal(t, wantStatus, gs.Status.Code, "Unsuccessful conversion %d", i) + } +} + +// ocBatches has the OpenCensus proto batches used in the test. They are hard coded because +// structs like tracepb.AttributeMap cannot be ready from JSON. +var ocBatchesFromZipkinV1 = []consumerdata.TraceData{ + { + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: "front-proxy"}, + Attributes: map[string]string{"ipv4": "172.31.0.2"}, + }, + Spans: []*tracepb.Span{ + { + TraceId: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + SpanId: []byte{0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + ParentSpanId: nil, + Name: &tracepb.TruncatableString{Value: "checkAvailability"}, + Kind: tracepb.Span_CLIENT, + StartTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 446743000}, + EndTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 459699000}, + TimeEvents: nil, + }, + }, + }, + { + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: "service1"}, + Attributes: map[string]string{"ipv4": "172.31.0.4"}, + }, + Spans: []*tracepb.Span{ + { + TraceId: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + SpanId: []byte{0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + ParentSpanId: nil, + Name: &tracepb.TruncatableString{Value: "checkAvailability"}, + Kind: tracepb.Span_SERVER, + StartTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 448081000}, + EndTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 460102000}, + TimeEvents: &tracepb.Span_TimeEvents{ + TimeEvent: []*tracepb.Span_TimeEvent{ + { + Time: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 450000000}, + Value: &tracepb.Span_TimeEvent_Annotation_{ + Annotation: &tracepb.Span_TimeEvent_Annotation{ + Description: &tracepb.TruncatableString{Value: "custom time event"}, + }, + }, + }, + }, + }, + }, + { + TraceId: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + SpanId: []byte{0xf9, 0xeb, 0xb6, 0xe6, 0x48, 0x80, 0x61, 0x2a}, + ParentSpanId: []byte{0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + Name: &tracepb.TruncatableString{Value: "checkStock"}, + Kind: tracepb.Span_CLIENT, + StartTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 453923000}, + EndTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 457663000}, + TimeEvents: nil, + }, + }, + }, + { + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: "service2"}, + Attributes: map[string]string{"ipv4": "172.31.0.7"}, + }, + Spans: []*tracepb.Span{ + { + TraceId: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + SpanId: []byte{0xf9, 0xeb, 0xb6, 0xe6, 0x48, 0x80, 0x61, 0x2a}, + ParentSpanId: []byte{0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + Name: &tracepb.TruncatableString{Value: "checkStock"}, + Kind: tracepb.Span_SERVER, + StartTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 454487000}, + EndTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 457320000}, + Status: &tracepb.Status{ + Code: 0, + }, + Attributes: &tracepb.Span_Attributes{ + AttributeMap: map[string]*tracepb.AttributeValue{ + "http.status_code": { + Value: &tracepb.AttributeValue_IntValue{IntValue: 200}, + }, + "http.url": { + Value: &tracepb.AttributeValue_StringValue{StringValue: &tracepb.TruncatableString{Value: "http://localhost:9000/trace/2"}}, + }, + "success": { + Value: &tracepb.AttributeValue_BoolValue{BoolValue: true}, + }, + "processed": { + Value: &tracepb.AttributeValue_DoubleValue{DoubleValue: 1.5}, + }, + }, + }, + TimeEvents: nil, + }, + }, + }, + { + Node: &commonpb.Node{ + ServiceInfo: &commonpb.ServiceInfo{Name: "unknown-service"}, + }, + Spans: []*tracepb.Span{ + { + TraceId: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + SpanId: []byte{0xfe, 0x35, 0x1a, 0x05, 0x3f, 0xbc, 0xac, 0x1f}, + ParentSpanId: []byte{0x0e, 0xd2, 0xe6, 0x3c, 0xbe, 0x71, 0xf5, 0xa8}, + Name: &tracepb.TruncatableString{Value: "checkStock"}, + Kind: tracepb.Span_SPAN_KIND_UNSPECIFIED, + StartTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 453923000}, + EndTime: ×tamppb.Timestamp{Seconds: 1544805927, Nanos: 457663000}, + Attributes: nil, + }, + }, + }, +} + +func TestSpanKindTranslation(t *testing.T) { + tests := []struct { + zipkinV1Kind string + zipkinV2Kind zipkinmodel.Kind + ocKind tracepb.Span_SpanKind + ocAttrSpanKind tracetranslator.OpenTracingSpanKind + jaegerSpanKind string + }{ + { + zipkinV1Kind: "cr", + zipkinV2Kind: zipkinmodel.Client, + ocKind: tracepb.Span_CLIENT, + jaegerSpanKind: "client", + }, + + { + zipkinV1Kind: "sr", + zipkinV2Kind: zipkinmodel.Server, + ocKind: tracepb.Span_SERVER, + jaegerSpanKind: "server", + }, + + { + zipkinV1Kind: "ms", + zipkinV2Kind: zipkinmodel.Producer, + ocKind: tracepb.Span_SPAN_KIND_UNSPECIFIED, + ocAttrSpanKind: tracetranslator.OpenTracingSpanKindProducer, + jaegerSpanKind: "producer", + }, + + { + zipkinV1Kind: "mr", + zipkinV2Kind: zipkinmodel.Consumer, + ocKind: tracepb.Span_SPAN_KIND_UNSPECIFIED, + ocAttrSpanKind: tracetranslator.OpenTracingSpanKindConsumer, + jaegerSpanKind: "consumer", + }, + } + + for _, test := range tests { + t.Run(test.zipkinV1Kind, func(t *testing.T) { + // Create Zipkin V1 span. + zSpan := &zipkinV1Span{ + TraceID: "1234567890123456", + ID: "0123456789123456", + Annotations: []*annotation{ + {Value: test.zipkinV1Kind}, // note that only first annotation matters. + {Value: "cr"}, // this will have no effect. + }, + } + + // Translate to OC and verify that span kind is correctly translated. + ocSpan, parsedAnnotations, err := zipkinV1ToOCSpan(zSpan, false) + assert.NoError(t, err) + assert.EqualValues(t, test.ocKind, ocSpan.Kind) + assert.NotNil(t, parsedAnnotations) + if test.ocAttrSpanKind != "" { + require.NotNil(t, ocSpan.Attributes) + // This is a special case, verify that TagSpanKind attribute is set. + expected := &tracepb.AttributeValue{ + Value: &tracepb.AttributeValue_StringValue{ + StringValue: &tracepb.TruncatableString{Value: string(test.ocAttrSpanKind)}, + }, + } + assert.EqualValues(t, expected, ocSpan.Attributes.AttributeMap[tracetranslator.TagSpanKind]) + } + }) + } +} + +func TestZipkinV1ToOCSpanInvalidTraceId(t *testing.T) { + zSpan := &zipkinV1Span{ + TraceID: "abc", + ID: "0123456789123456", + Annotations: []*annotation{ + {Value: "cr"}, + }, + } + _, _, err := zipkinV1ToOCSpan(zSpan, false) + assert.EqualError(t, err, "zipkinV1 span traceId: hex traceId span has wrong length (expected 16 or 32)") +} + +func TestZipkinV1ToOCSpanInvalidSpanId(t *testing.T) { + zSpan := &zipkinV1Span{ + TraceID: "1234567890123456", + ID: "abc", + Annotations: []*annotation{ + {Value: "cr"}, + }, + } + _, _, err := zipkinV1ToOCSpan(zSpan, false) + assert.EqualError(t, err, "zipkinV1 span id: hex Id has wrong length (expected 16)") +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces.go new file mode 100644 index 00000000000..a323123764c --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/internaldata" +) + +func V1JSONBatchToInternalTraces(blob []byte, parseStringTags bool) (pdata.Traces, error) { + traces := pdata.NewTraces() + + ocTraces, err := v1JSONBatchToOCProto(blob, parseStringTags) + if err != nil { + return traces, err + } + + for _, td := range ocTraces { + tmp := internaldata.OCToTraceData(td) + tmp.ResourceSpans().MoveAndAppendTo(traces.ResourceSpans()) + } + return traces, nil +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces_test.go b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces_test.go new file mode 100644 index 00000000000..e91d2b1c734 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv1_to_traces_test.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSingleJSONV1BatchToTraces(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_single_batch.json") + require.NoError(t, err, "Failed to load test data") + + got, err := V1JSONBatchToInternalTraces(blob, false) + require.NoError(t, err, "Failed to translate zipkinv1 to OC proto") + + assert.Equal(t, 5, got.SpanCount()) +} + +func TestErrorSpanToTraces(t *testing.T) { + blob, err := ioutil.ReadFile("./testdata/zipkin_v1_error_batch.json") + require.NoError(t, err, "Failed to load test data") + + td, err := V1JSONBatchToInternalTraces(blob, false) + assert.Error(t, err, "Should have generated error") + assert.NotNil(t, td) +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces.go b/internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces.go new file mode 100644 index 00000000000..91f18efe667 --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces.go @@ -0,0 +1,427 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "encoding/json" + "fmt" + "math" + "sort" + "strconv" + "strings" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/data" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" +) + +var nonSpanAttributes = getNonSpanAttributes() + +func getNonSpanAttributes() map[string]struct{} { + attrs := make(map[string]struct{}) + for _, key := range conventions.GetResourceSemanticConventionAttributeNames() { + attrs[key] = struct{}{} + } + attrs[tracetranslator.TagServiceNameSource] = struct{}{} + attrs[tracetranslator.TagInstrumentationName] = struct{}{} + attrs[tracetranslator.TagInstrumentationVersion] = struct{}{} + attrs[conventions.OCAttributeProcessStartTime] = struct{}{} + attrs[conventions.OCAttributeExporterVersion] = struct{}{} + attrs[conventions.OCAttributeProcessID] = struct{}{} + attrs[conventions.OCAttributeResourceType] = struct{}{} + return attrs +} + +// Custom Sort on +type byOTLPTypes []*zipkinmodel.SpanModel + +func (b byOTLPTypes) Len() int { + return len(b) +} + +func (b byOTLPTypes) Less(i, j int) bool { + diff := strings.Compare(extractLocalServiceName(b[i]), extractLocalServiceName(b[j])) + if diff != 0 { + return diff <= 0 + } + diff = strings.Compare(extractInstrumentationLibrary(b[i]), extractInstrumentationLibrary(b[j])) + return diff <= 0 +} + +func (b byOTLPTypes) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +// V2SpansToInternalTraces translates Zipkin v2 spans into internal trace data. +func V2SpansToInternalTraces(zipkinSpans []*zipkinmodel.SpanModel, parseStringTags bool) (pdata.Traces, error) { + traceData := pdata.NewTraces() + if len(zipkinSpans) == 0 { + return traceData, nil + } + + sort.Sort(byOTLPTypes(zipkinSpans)) + + rss := traceData.ResourceSpans() + prevServiceName := "" + prevInstrLibName := "" + rsCount := rss.Len() + ilsCount := 0 + spanCount := 0 + var curRscSpans pdata.ResourceSpans + var curILSpans pdata.InstrumentationLibrarySpans + var curSpans pdata.SpanSlice + for _, zspan := range zipkinSpans { + if zspan == nil { + continue + } + tags := copySpanTags(zspan.Tags) + localServiceName := extractLocalServiceName(zspan) + if localServiceName != prevServiceName { + prevServiceName = localServiceName + rss.Resize(rsCount + 1) + curRscSpans = rss.At(rsCount) + rsCount++ + populateResourceFromZipkinSpan(tags, localServiceName, curRscSpans.Resource()) + prevInstrLibName = "" + ilsCount = 0 + } + instrLibName := extractInstrumentationLibrary(zspan) + if instrLibName != prevInstrLibName || ilsCount == 0 { + prevInstrLibName = instrLibName + curRscSpans.InstrumentationLibrarySpans().Resize(ilsCount + 1) + curILSpans = curRscSpans.InstrumentationLibrarySpans().At(ilsCount) + ilsCount++ + populateILFromZipkinSpan(tags, instrLibName, curILSpans.InstrumentationLibrary()) + spanCount = 0 + curSpans = curILSpans.Spans() + } + curSpans.Resize(spanCount + 1) + err := zSpanToInternal(zspan, tags, curSpans.At(spanCount), parseStringTags) + if err != nil { + return traceData, err + } + spanCount++ + } + + return traceData, nil +} + +func zSpanToInternal(zspan *zipkinmodel.SpanModel, tags map[string]string, dest pdata.Span, parseStringTags bool) error { + dest.SetTraceID(tracetranslator.UInt64ToTraceID(zspan.TraceID.High, zspan.TraceID.Low)) + dest.SetSpanID(tracetranslator.UInt64ToSpanID(uint64(zspan.ID))) + if value, ok := tags[tracetranslator.TagW3CTraceState]; ok { + dest.SetTraceState(pdata.TraceState(value)) + delete(tags, tracetranslator.TagW3CTraceState) + } + parentID := zspan.ParentID + if parentID != nil && *parentID != zspan.ID { + dest.SetParentSpanID(tracetranslator.UInt64ToSpanID(uint64(*parentID))) + } + + dest.SetName(zspan.Name) + startNano := zspan.Timestamp.UnixNano() + dest.SetStartTime(pdata.TimestampUnixNano(startNano)) + dest.SetEndTime(pdata.TimestampUnixNano(startNano + zspan.Duration.Nanoseconds())) + dest.SetKind(zipkinKindToSpanKind(zspan.Kind, tags)) + + populateSpanStatus(tags, dest.Status()) + if err := zTagsToSpanLinks(tags, dest.Links()); err != nil { + return err + } + + attrs := dest.Attributes() + attrs.InitEmptyWithCapacity(len(tags)) + if err := zTagsToInternalAttrs(zspan, tags, attrs, parseStringTags); err != nil { + return err + } + + err := populateSpanEvents(zspan, dest.Events()) + return err +} + +func populateSpanStatus(tags map[string]string, status pdata.SpanStatus) { + if value, ok := tags[tracetranslator.TagStatusCode]; ok { + status.SetCode(pdata.StatusCode(otlptrace.Status_StatusCode_value[value])) + delete(tags, tracetranslator.TagStatusCode) + if value, ok := tags[tracetranslator.TagStatusMsg]; ok { + status.SetMessage(value) + delete(tags, tracetranslator.TagStatusMsg) + } + } +} + +func zipkinKindToSpanKind(kind zipkinmodel.Kind, tags map[string]string) pdata.SpanKind { + switch kind { + case zipkinmodel.Client: + return pdata.SpanKindCLIENT + case zipkinmodel.Server: + return pdata.SpanKindSERVER + case zipkinmodel.Producer: + return pdata.SpanKindPRODUCER + case zipkinmodel.Consumer: + return pdata.SpanKindCONSUMER + default: + if value, ok := tags[tracetranslator.TagSpanKind]; ok { + delete(tags, tracetranslator.TagSpanKind) + if value == "internal" { + return pdata.SpanKindINTERNAL + } + } + return pdata.SpanKindUNSPECIFIED + } +} + +func zTagsToSpanLinks(tags map[string]string, dest pdata.SpanLinkSlice) error { + index := 0 + for i := 0; i < 128; i++ { + key := fmt.Sprintf("otlp.link.%d", i) + val, ok := tags[key] + if !ok { + return nil + } + delete(tags, key) + + parts := strings.Split(val, "|") + partCnt := len(parts) + if partCnt < 5 { + continue + } + dest.Resize(index + 1) + link := dest.At(index) + index++ + + // Convert trace id. + rawTrace := data.TraceID{} + errTrace := rawTrace.UnmarshalJSON([]byte(parts[0])) + if errTrace != nil { + return errTrace + } + link.SetTraceID(pdata.TraceID(rawTrace)) + + // Convert span id. + rawSpan := data.SpanID{} + errSpan := rawSpan.UnmarshalJSON([]byte(parts[1])) + if errSpan != nil { + return errSpan + } + link.SetSpanID(pdata.SpanID(rawSpan)) + + link.SetTraceState(pdata.TraceState(parts[2])) + + var jsonStr string + if partCnt == 5 { + jsonStr = parts[3] + } else { + jsonParts := parts[3 : partCnt-1] + jsonStr = strings.Join(jsonParts, "|") + } + var attrs map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &attrs); err != nil { + return err + } + if err := jsonMapToAttributeMap(attrs, link.Attributes()); err != nil { + return err + } + + dropped, errDropped := strconv.ParseUint(parts[partCnt-1], 10, 32) + if errDropped != nil { + return errDropped + } + link.SetDroppedAttributesCount(uint32(dropped)) + } + return nil +} + +func populateSpanEvents(zspan *zipkinmodel.SpanModel, events pdata.SpanEventSlice) error { + events.Resize(len(zspan.Annotations)) + for ix, anno := range zspan.Annotations { + event := events.At(ix) + startNano := anno.Timestamp.UnixNano() + event.SetTimestamp(pdata.TimestampUnixNano(startNano)) + + parts := strings.Split(anno.Value, "|") + partCnt := len(parts) + event.SetName(parts[0]) + if partCnt < 3 { + continue + } + + var jsonStr string + if partCnt == 3 { + jsonStr = parts[1] + } else { + jsonParts := parts[1 : partCnt-1] + jsonStr = strings.Join(jsonParts, "|") + } + var attrs map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &attrs); err != nil { + return err + } + if err := jsonMapToAttributeMap(attrs, event.Attributes()); err != nil { + return err + } + + dropped, errDropped := strconv.ParseUint(parts[partCnt-1], 10, 32) + if errDropped != nil { + return errDropped + } + event.SetDroppedAttributesCount(uint32(dropped)) + } + return nil +} + +func jsonMapToAttributeMap(attrs map[string]interface{}, dest pdata.AttributeMap) error { + for key, val := range attrs { + if s, ok := val.(string); ok { + dest.InsertString(key, s) + } else if d, ok := val.(float64); ok { + if math.Mod(d, 1.0) == 0.0 { + dest.InsertInt(key, int64(d)) + } else { + dest.InsertDouble(key, d) + } + } else if b, ok := val.(bool); ok { + dest.InsertBool(key, b) + } + } + return nil +} + +func zTagsToInternalAttrs(zspan *zipkinmodel.SpanModel, tags map[string]string, dest pdata.AttributeMap, parseStringTags bool) error { + parseErr := tagsToAttributeMap(tags, dest, parseStringTags) + if zspan.LocalEndpoint != nil { + if zspan.LocalEndpoint.IPv4 != nil { + dest.InsertString(conventions.AttributeNetHostIP, zspan.LocalEndpoint.IPv4.String()) + } + if zspan.LocalEndpoint.IPv6 != nil { + dest.InsertString(conventions.AttributeNetHostIP, zspan.LocalEndpoint.IPv6.String()) + } + if zspan.LocalEndpoint.Port > 0 { + dest.UpsertInt(conventions.AttributeNetHostPort, int64(zspan.LocalEndpoint.Port)) + } + } + if zspan.RemoteEndpoint != nil { + if zspan.RemoteEndpoint.ServiceName != "" { + dest.InsertString(conventions.AttributePeerService, zspan.RemoteEndpoint.ServiceName) + } + if zspan.RemoteEndpoint.IPv4 != nil { + dest.InsertString(conventions.AttributeNetPeerIP, zspan.RemoteEndpoint.IPv4.String()) + } + if zspan.RemoteEndpoint.IPv6 != nil { + dest.InsertString(conventions.AttributeNetPeerIP, zspan.RemoteEndpoint.IPv6.String()) + } + if zspan.RemoteEndpoint.Port > 0 { + dest.UpsertInt(conventions.AttributeNetPeerPort, int64(zspan.RemoteEndpoint.Port)) + } + } + return parseErr +} + +func tagsToAttributeMap(tags map[string]string, dest pdata.AttributeMap, parseStringTags bool) error { + var parseErr error + for key, val := range tags { + if _, ok := nonSpanAttributes[key]; ok { + continue + } + + if parseStringTags { + switch tracetranslator.DetermineValueType(val, false) { + case pdata.AttributeValueINT: + iValue, _ := strconv.ParseInt(val, 10, 64) + dest.UpsertInt(key, iValue) + case pdata.AttributeValueDOUBLE: + fValue, _ := strconv.ParseFloat(val, 64) + dest.UpsertDouble(key, fValue) + case pdata.AttributeValueBOOL: + bValue, _ := strconv.ParseBool(val) + dest.UpsertBool(key, bValue) + default: + dest.UpsertString(key, val) + } + } else { + dest.UpsertString(key, val) + } + } + return parseErr +} + +func populateResourceFromZipkinSpan(tags map[string]string, localServiceName string, resource pdata.Resource) { + if localServiceName == tracetranslator.ResourceNoServiceName { + return + } + + if len(tags) == 0 { + resource.Attributes().InsertString(conventions.AttributeServiceName, localServiceName) + return + } + + snSource := tags[tracetranslator.TagServiceNameSource] + if snSource == "" { + resource.Attributes().InsertString(conventions.AttributeServiceName, localServiceName) + } else { + resource.Attributes().InsertString(snSource, localServiceName) + } + delete(tags, tracetranslator.TagServiceNameSource) + + for key := range getNonSpanAttributes() { + if key == tracetranslator.TagInstrumentationName || key == tracetranslator.TagInstrumentationVersion { + continue + } + if value, ok := tags[key]; ok { + resource.Attributes().UpsertString(key, value) + delete(tags, key) + } + } +} + +func populateILFromZipkinSpan(tags map[string]string, instrLibName string, library pdata.InstrumentationLibrary) { + if instrLibName == "" { + return + } + if value, ok := tags[tracetranslator.TagInstrumentationName]; ok { + library.SetName(value) + delete(tags, tracetranslator.TagInstrumentationName) + } + if value, ok := tags[tracetranslator.TagInstrumentationVersion]; ok { + library.SetVersion(value) + delete(tags, tracetranslator.TagInstrumentationVersion) + } +} + +func copySpanTags(tags map[string]string) map[string]string { + dest := make(map[string]string, len(tags)) + for key, val := range tags { + dest[key] = val + } + return dest +} + +func extractLocalServiceName(zspan *zipkinmodel.SpanModel) string { + if zspan == nil || zspan.LocalEndpoint == nil || zspan.LocalEndpoint.ServiceName == "" { + return tracetranslator.ResourceNoServiceName + } + return zspan.LocalEndpoint.ServiceName +} + +func extractInstrumentationLibrary(zspan *zipkinmodel.SpanModel) string { + if zspan == nil || len(zspan.Tags) == 0 { + return "" + } + return zspan.Tags[tracetranslator.TagInstrumentationName] +} diff --git a/internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces_test.go b/internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces_test.go new file mode 100644 index 00000000000..eb81a52866a --- /dev/null +++ b/internal/otel_collector/translator/trace/zipkin/zipkinv2_to_traces_test.go @@ -0,0 +1,130 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zipkin + +import ( + "testing" + "time" + + zipkinmodel "github.com/openzipkin/zipkin-go/model" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/internal/testdata" + "go.opentelemetry.io/collector/translator/conventions" +) + +func TestZipkinSpansToInternalTraces(t *testing.T) { + tests := []struct { + name string + zs []*zipkinmodel.SpanModel + td pdata.Traces + err error + }{ + { + name: "empty", + zs: make([]*zipkinmodel.SpanModel, 0), + td: testdata.GenerateTraceDataEmpty(), + err: nil, + }, + { + name: "nilSpan", + zs: generateNilSpan(), + td: testdata.GenerateTraceDataEmpty(), + err: nil, + }, + { + name: "minimalSpan", + zs: generateSpanNoEndpoints(), + td: generateTraceSingleSpanNoResourceOrInstrLibrary(), + err: nil, + }, + { + name: "onlyLocalEndpointSpan", + zs: generateSpanNoTags(), + td: generateTraceSingleSpanMinmalResource(), + err: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + td, err := V2SpansToInternalTraces(test.zs, false) + assert.EqualValues(t, test.err, err) + if test.name != "nilSpan" { + assert.Equal(t, len(test.zs), td.SpanCount()) + } + assert.EqualValues(t, test.td, td) + }) + } +} + +func generateNilSpan() []*zipkinmodel.SpanModel { + return make([]*zipkinmodel.SpanModel, 1) +} + +func generateSpanNoEndpoints() []*zipkinmodel.SpanModel { + spans := make([]*zipkinmodel.SpanModel, 1) + spans[0] = &zipkinmodel.SpanModel{ + SpanContext: zipkinmodel.SpanContext{ + TraceID: convertTraceID( + pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})), + ID: convertSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})), + }, + Name: "MinimalData", + Kind: zipkinmodel.Client, + Timestamp: time.Unix(1596911098, 294000000), + Duration: 1000000, + Shared: false, + LocalEndpoint: nil, + RemoteEndpoint: nil, + Annotations: nil, + Tags: nil, + } + return spans +} + +func generateSpanNoTags() []*zipkinmodel.SpanModel { + spans := generateSpanNoEndpoints() + spans[0].LocalEndpoint = &zipkinmodel.Endpoint{ServiceName: "SoleAttr"} + return spans +} + +func generateTraceSingleSpanNoResourceOrInstrLibrary() pdata.Traces { + td := pdata.NewTraces() + td.ResourceSpans().Resize(1) + rs := td.ResourceSpans().At(0) + rs.InstrumentationLibrarySpans().Resize(1) + ils := rs.InstrumentationLibrarySpans().At(0) + ils.Spans().Resize(1) + span := ils.Spans().At(0) + span.SetTraceID( + pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})) + span.SetSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})) + span.SetName("MinimalData") + span.SetKind(pdata.SpanKindCLIENT) + span.SetStartTime(1596911098294000000) + span.SetEndTime(1596911098295000000) + span.Attributes().InitEmptyWithCapacity(0) + return td +} + +func generateTraceSingleSpanMinmalResource() pdata.Traces { + td := generateTraceSingleSpanNoResourceOrInstrLibrary() + rs := td.ResourceSpans().At(0) + rsc := rs.Resource() + rsc.Attributes().InitEmptyWithCapacity(1) + rsc.Attributes().UpsertString(conventions.AttributeServiceName, "SoleAttr") + return td +} From 8f1d7d00ebb5d16e266b48579f96a20c184ede46 Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Thu, 28 Jan 2021 17:03:16 +0800 Subject: [PATCH 3/3] Fix goimports --- tools.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools.go b/tools.go index fc786c281c6..e48cf3d26ff 100644 --- a/tools.go +++ b/tools.go @@ -27,7 +27,7 @@ import ( _ "github.com/jstemmer/go-junit-report" _ "github.com/reviewdog/reviewdog" _ "github.com/t-yuki/gocover-cobertura" + _ "go.elastic.co/go-licence-detector" _ "github.com/elastic/go-licenser" - _ "go.elastic.co/go-licence-detector" )

$ik7gwCd=5mcVFQ+M)PVH`uw7LNU& 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